我在将UIKit架构模式转换为SwiftUI时遇到问题。我目前的模式主要是带有协调器/路由器的MVVM。通过添加@ ObservableObject / @ Published,MVVM部分看起来非常简单自然。但是,协调/路由似乎并不直观。视图和协调(导航)功能在SwiftUI中紧密结合在一起。似乎实际上不可能将它们与使用helper结构分开AnyView。
AnyView
这里是一个示例:我想在SwiftUI中创建可重用的行/单元。可以说生产中的这一行非常复杂,因此我想重用它。我也希望将其放置在另一个模块中,以便可以在多个目标中重复使用它。(例如iOS,macCatalyst等)
现在,我想控制当用户点击该视图或该视图中的按钮时发生的情况。根据上下文,我需要导航到不同的目的地。据我所知,可能的NavigationLink目标必须 硬连线 到视图中或AnyView必须传递到视图中。
这里有一些示例代码。该单元格/行包含两个按钮。我想导航到其他依赖于上下文的视图,而不要硬连接到代码中:
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中的主要设计缺陷。除了使用AnyViewhack 之外,带有导航链接的可重用组件基本上是不可能的。据我所知AnyView,它只用于需要类型擦除的特定用例,并且在性能上有很多缺点。因此,我不认为这是使用SwiftUI创建可重用,可导航视图的惯用解决方案。
这真的是唯一的解决方案吗?也许我完全错了,这始终是错误的方向。我读过某处(找不到该帖子..)有关使用某种中央状态的信息,该状态指示了要显示的视图,但是我没有看到具体的示例如何执行此操作。
第二个挑战:我也不希望单元格对其他任何轻按然后对按钮做出反应。但是似乎无法控制如果点击该单元格导航到的位置。(因此,请不要点击按钮之一,而要在单元格中的任何位置)在当前示例代码中,它(出于任何原因)导航到“目标2”。
提前致谢。
最好在行中使用泛型,如下所示(已通过Xcode 11.4测试)
用法示例:
ProductFamilyRow(item: ProductFamilyItem(title: "Test"), destinationView1: { Text("Details1") }, destinationView2: { Text("Details2") })
接口:
更新 -为行突出显示添加了块。列表自动检测行内的按钮或链接,并突出显示是否存在任何标准(!键)。因此,要禁用此类行为,需要将所有内容隐藏在自定义按钮样式下。
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()) } }