SwiftUI MVVM Coordinator/Router/NavigationLink
SwiftUI MVVM Coordinator/Router/NavigationLink
我在将 UIKit 架构模式转换为 SwiftUI 时遇到问题。我目前的模式主要是带有 Coordinators/Routers 的 MVVM。添加 @ObservableObject/@Published 后,MVVM 部分看起来非常简单自然。但是 coordinating/routing 似乎不直观。视图和协调(导航)功能在 SwiftUI 中紧密耦合。似乎真的不可能将它们与使用辅助结构 AnyView
分开。
举个例子:
我想在 SwiftUI 中创建一个可重用的 row/cell。假设生产中的这一行非常复杂,因此我想重用它。我也想把它放在另一个模块中,这样我就可以在多个目标中重用它。 (如 iOS、macCatalyst 等...)
现在我想控制当用户点击该视图或该视图中的按钮时发生的情况。根据上下文,我需要导航到不同的目的地。据我所知,可能的 NavigationLink 目标必须 硬连线 到视图中,或者必须将 AnyView
传递到视图中。
这里是一些示例代码。这个 cell/row 包含两个按钮。我想导航到依赖于上下文的其他视图,而不是硬连接到代码中:
struct ProductFamilyRow: View {
@State private var selection: Int? = 0
let item: ProductFamilyItem
let destinationView1: AnyView
let destinationView2: AnyView
var body: some View {
VStack {
NavigationLink(
destination: destinationView1,
tag: 1,
selection: self.$selection
) {
EmptyView()
}
NavigationLink(
destination: destinationView2,
tag: 2,
selection: self.$selection
) {
EmptyView()
}
HStack {
Text(item.title)
Button("Destination 1") {
self.selection = 1
}.foregroundColor(Color.blue)
Button("Destination 2") {
self.selection = 2
}.foregroundColor(Color.blue)
}
//Image(item.image)
}.buttonStyle(PlainButtonStyle())
}
}
这似乎是 SwiftUI 的一个主要设计缺陷。除了使用 AnyView
hack 之外,基本上不可能具有导航链接的可重用组件。据我所知,AnyView
仅用于我需要类型擦除的特定用例,并且具有相当多的性能缺陷。所以我不认为这是使用 SwiftUI 创建可重用、可导航视图的惯用解决方案。
这真的是唯一的解决办法吗?也许我完全错了,无论如何这是错误的方向。我在某处读到(再也找不到 post..)关于使用一些指示要显示哪个视图的中央状态,但我没有看到如何执行此操作的具体示例。
第二个挑战:
此外,我不希望细胞对按钮上的任何其他水龙头做出反应。但是,如果点击,似乎无法控制单元格导航到的位置。 (所以不是点击其中一个按钮,而是点击单元格中的任何位置)在当前示例代码中,它导航(出于任何原因)到 "Destination 2"。
提前致谢。
最好为您的行使用泛型,如下所示(使用 Xcode 11.4 测试)
用法示例:
ProductFamilyRow(item: ProductFamilyItem(title: "Test"),
destinationView1: { Text("Details1") },
destinationView2: { Text("Details2") })
接口:
更新 - 为行高亮添加块。列表自动检测行内的按钮或 link 并突出显示是否存在任何标准 (!key)。因此,要禁用此类行为,它需要在自定义按钮样式下隐藏所有内容。
struct ProductFamilyRowStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.colorMultiply(configuration.isPressed ?
Color.white.opacity(0.5) : Color.white) // any effect you want
}
}
struct ProductFamilyRow<D1: View, D2: View>: View {
let item: ProductFamilyItem
let destinationView1: () -> D1
let destinationView2: () -> D2
init(item: ProductFamilyItem, @ViewBuilder destinationView1: @escaping () -> D1,
@ViewBuilder destinationView2: @escaping () -> D2)
{
self.item = item
self.destinationView1 = destinationView1
self.destinationView2 = destinationView2
}
@State private var selection: Int? = 0
var body: some View {
VStack {
HStack {
Text(item.title)
Button(action: {
self.selection = 1
}) {
Text("Destination 1")
.background( // hide link inside button !!
NavigationLink(destination: destinationView1(),
tag: 1, selection: self.$selection) { EmptyView() }
)
}.foregroundColor(Color.blue)
Button(action: {
self.selection = 2
}) {
Text("Destination 2")
.background(
NavigationLink(destination: destinationView2(),
tag: 2, selection: self.$selection) { EmptyView() }
)
}.foregroundColor(Color.blue)
}
//Image(item.image)
}.frame(maxWidth: .infinity) // to have container centered
.buttonStyle(ProductFamilyRowStyle())
}
}
我在将 UIKit 架构模式转换为 SwiftUI 时遇到问题。我目前的模式主要是带有 Coordinators/Routers 的 MVVM。添加 @ObservableObject/@Published 后,MVVM 部分看起来非常简单自然。但是 coordinating/routing 似乎不直观。视图和协调(导航)功能在 SwiftUI 中紧密耦合。似乎真的不可能将它们与使用辅助结构 AnyView
分开。
举个例子: 我想在 SwiftUI 中创建一个可重用的 row/cell。假设生产中的这一行非常复杂,因此我想重用它。我也想把它放在另一个模块中,这样我就可以在多个目标中重用它。 (如 iOS、macCatalyst 等...)
现在我想控制当用户点击该视图或该视图中的按钮时发生的情况。根据上下文,我需要导航到不同的目的地。据我所知,可能的 NavigationLink 目标必须 硬连线 到视图中,或者必须将 AnyView
传递到视图中。
这里是一些示例代码。这个 cell/row 包含两个按钮。我想导航到依赖于上下文的其他视图,而不是硬连接到代码中:
struct ProductFamilyRow: View {
@State private var selection: Int? = 0
let item: ProductFamilyItem
let destinationView1: AnyView
let destinationView2: AnyView
var body: some View {
VStack {
NavigationLink(
destination: destinationView1,
tag: 1,
selection: self.$selection
) {
EmptyView()
}
NavigationLink(
destination: destinationView2,
tag: 2,
selection: self.$selection
) {
EmptyView()
}
HStack {
Text(item.title)
Button("Destination 1") {
self.selection = 1
}.foregroundColor(Color.blue)
Button("Destination 2") {
self.selection = 2
}.foregroundColor(Color.blue)
}
//Image(item.image)
}.buttonStyle(PlainButtonStyle())
}
}
这似乎是 SwiftUI 的一个主要设计缺陷。除了使用 AnyView
hack 之外,基本上不可能具有导航链接的可重用组件。据我所知,AnyView
仅用于我需要类型擦除的特定用例,并且具有相当多的性能缺陷。所以我不认为这是使用 SwiftUI 创建可重用、可导航视图的惯用解决方案。
这真的是唯一的解决办法吗?也许我完全错了,无论如何这是错误的方向。我在某处读到(再也找不到 post..)关于使用一些指示要显示哪个视图的中央状态,但我没有看到如何执行此操作的具体示例。
第二个挑战: 此外,我不希望细胞对按钮上的任何其他水龙头做出反应。但是,如果点击,似乎无法控制单元格导航到的位置。 (所以不是点击其中一个按钮,而是点击单元格中的任何位置)在当前示例代码中,它导航(出于任何原因)到 "Destination 2"。
提前致谢。
最好为您的行使用泛型,如下所示(使用 Xcode 11.4 测试)
用法示例:
ProductFamilyRow(item: ProductFamilyItem(title: "Test"),
destinationView1: { Text("Details1") },
destinationView2: { Text("Details2") })
接口:
更新 - 为行高亮添加块。列表自动检测行内的按钮或 link 并突出显示是否存在任何标准 (!key)。因此,要禁用此类行为,它需要在自定义按钮样式下隐藏所有内容。
struct ProductFamilyRowStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.colorMultiply(configuration.isPressed ?
Color.white.opacity(0.5) : Color.white) // any effect you want
}
}
struct ProductFamilyRow<D1: View, D2: View>: View {
let item: ProductFamilyItem
let destinationView1: () -> D1
let destinationView2: () -> D2
init(item: ProductFamilyItem, @ViewBuilder destinationView1: @escaping () -> D1,
@ViewBuilder destinationView2: @escaping () -> D2)
{
self.item = item
self.destinationView1 = destinationView1
self.destinationView2 = destinationView2
}
@State private var selection: Int? = 0
var body: some View {
VStack {
HStack {
Text(item.title)
Button(action: {
self.selection = 1
}) {
Text("Destination 1")
.background( // hide link inside button !!
NavigationLink(destination: destinationView1(),
tag: 1, selection: self.$selection) { EmptyView() }
)
}.foregroundColor(Color.blue)
Button(action: {
self.selection = 2
}) {
Text("Destination 2")
.background(
NavigationLink(destination: destinationView2(),
tag: 2, selection: self.$selection) { EmptyView() }
)
}.foregroundColor(Color.blue)
}
//Image(item.image)
}.frame(maxWidth: .infinity) // to have container centered
.buttonStyle(ProductFamilyRowStyle())
}
}