我将尝试一一回答您的观点。我将遵循一个小例子,我们的 View that should be reusable 是一个简单View
的,显示 aText
和 aNavigationLink
将转到 some Destination
。如果您想查看我的完整示例,我创建了一个要点:SwiftUI - 使用协调器进行灵活导航
。
设计问题:NavigationLinks 被硬编码到视图中。
在您的示例中,它绑定到 View 但正如其他答案已经显示的那样,您可以将目标注入您的 View type struct MyView<Destination: View>: View
。您现在可以使用任何符合 View 的 Type 作为您的目的地。
但是,如果包含此 NavigationLink 的视图应该是可重用的,我就无法对目的地进行硬编码。必须有一种机制来提供目的地。
通过上述更改,有提供类型的机制。一个例子是:
struct BoldTextView: View {
var text: String
var body: some View {
Text(text)
.bold()
}
}
struct NotReusableTextView: View {
var text: String
var body: some View {
VStack {
Text(text)
NavigationLink("Link", destination: BoldTextView(text: text))
}
}
}
将更改为
struct ReusableNavigationLinkTextView<Destination: View>: View {
var text: String
var destination: () -> Destination
var body: some View {
VStack {
Text(text)
NavigationLink("Link", destination: self.destination())
}
}
}
你可以像这样传递你的目的地:
struct BoldNavigationLink: View {
let text = "Text"
var body: some View {
ReusableNavigationLinkTextView(
text: self.text,
destination: { BoldTextView(text: self.text) }
)
}
}
一旦我有多个可重复使用的屏幕,我就会遇到一个逻辑问题,即一个可重复使用的视图 (ViewA) 需要一个预配置的视图目标 (ViewB)。但是如果 ViewB 还需要一个预配置的视图目标 ViewC 怎么办?在将 ViewB 注入 ViewA 之前,我需要以这样的方式创建 ViewB,即 ViewC 已经注入到 ViewB 中。等等....
好吧,显然您需要某种逻辑来确定您的Destination
. 在某些时候,您需要告诉视图接下来会出现什么视图。我想您要避免的是:
struct NestedMainView: View {
@State var text: String
var body: some View {
ReusableNavigationLinkTextView(
text: self.text,
destination: {
ReusableNavigationLinkTextView(
text: self.text,
destination: {
BoldTextView(text: self.text)
}
)
}
)
}
}
我整理了一个简单的示例,它使用Coordinator
s 传递依赖关系并创建视图。Coordinator 有一个协议,您可以在此基础上实现特定的用例。
protocol ReusableNavigationLinkTextViewCoordinator {
associatedtype Destination: View
var destination: () -> Destination { get }
func createView() -> ReusableNavigationLinkTextView<Destination>
}
现在我们可以创建一个特定的协调器,BoldTextView
当点击NavigationLink
.
struct ReusableNavigationLinkShowBoldViewCoordinator: ReusableNavigationLinkTextViewCoordinator {
@Binding var text: String
var destination: () -> BoldTextView {
{ return BoldTextView(text: self.text) }
}
func createView() -> ReusableNavigationLinkTextView<Destination> {
return ReusableNavigationLinkTextView(text: self.text, destination: self.destination)
}
}
如果需要,您还可以使用Coordinator
来实现确定视图目标的自定义逻辑。以下 Coordinator 显示了ItalicTextView
在链接上的四次点击之后。
struct ItalicTextView: View {
var text: String
var body: some View {
Text(text)
.italic()
}
}
struct ShowNavigationLinkUntilNumberGreaterFourThenItalicViewCoordinator: ReusableNavigationLinkTextViewCoordinator {
@Binding var text: String
let number: Int
private var isNumberGreaterThan4: Bool {
return number > 4
}
var destination: () -> AnyView {
{
if self.isNumberGreaterThan4 {
let coordinator = ItalicTextViewCoordinator(text: self.text)
return AnyView(
coordinator.createView()
)
} else {
let coordinator = ShowNavigationLinkUntilNumberGreaterFourThenItalicViewCoordinator(
text: self.$text,
number: self.number + 1
)
return AnyView(coordinator.createView())
}
}
}
func createView() -> ReusableNavigationLinkTextView<AnyView> {
return ReusableNavigationLinkTextView(text: self.text, destination: self.destination)
}
}
如果您有需要传递的数据,请在另一个协调器周围创建另一个 Coordinator 来保存该值。在这个例子中,我有一个TextField
-> EmptyView
->Text
应该将 TextField 中的值传递给Text.
TheEmptyView
必须没有此信息。
struct TextFieldView<Destination: View>: View {
@Binding var text: String
var destination: () -> Destination
var body: some View {
VStack {
TextField("Text", text: self.$text)
NavigationLink("Next", destination: self.destination())
}
}
}
struct EmptyNavigationLinkView<Destination: View>: View {
var destination: () -> Destination
var body: some View {
NavigationLink("Next", destination: self.destination())
}
}
这是通过调用其他协调器(或创建视图本身)来创建视图的协调器。它将值从TextField
to传递,Text
而 theEmptyView
不知道这一点。
struct TextFieldEmptyReusableViewCoordinator {
@Binding var text: String
func createView() -> some View {
let reusableViewBoldCoordinator = ReusableNavigationLinkShowBoldViewCoordinator(text: self.$text)
let reusableView = reusableViewBoldCoordinator.createView()
let emptyView = EmptyNavigationLinkView(destination: { reusableView })
let textField = TextFieldView(text: self.$text, destination: { emptyView })
return textField
}
}
总而言之,您还可以创建一个MainView
具有一些逻辑来决定应该使用什么View
/的东西。Coordinator
struct MainView: View {
@State var text = "Main"
var body: some View {
NavigationView {
VStack(spacing: 32) {
NavigationLink("Bold", destination: self.reuseThenBoldChild())
NavigationLink("Reuse then Italic", destination: self.reuseThenItalicChild())
NavigationLink("Greater Four", destination: self.numberGreaterFourChild())
NavigationLink("Text Field", destination: self.textField())
}
}
}
func reuseThenBoldChild() -> some View {
let coordinator = ReusableNavigationLinkShowBoldViewCoordinator(text: self.$text)
return coordinator.createView()
}
func reuseThenItalicChild() -> some View {
let coordinator = ReusableNavigationLinkShowItalicViewCoordinator(text: self.$text)
return coordinator.createView()
}
func numberGreaterFourChild() -> some View {
let coordinator = ShowNavigationLinkUntilNumberGreaterFourThenItalicViewCoordinator(text: self.$text, number: 1)
return coordinator.createView()
}
func textField() -> some View {
let coordinator = TextFieldEmptyReusableViewCoordinator(text: self.$text)
return coordinator.createView()
}
}
我知道我也可以创建一个Coordinator
协议和一些基本方法,但我想展示一个简单的例子来说明如何使用它们。
顺便说一句,这与我Coordinator
在 SwiftUIKit
应用程序中使用的方式非常相似。
如果您有任何问题、反馈或需要改进的地方,请告诉我。