我试图为一个更大、生产就绪的SwiftUI应用做架构。然而,我总是遇到相同的问题,这指向了SwiftUI中的一个主要设计缺陷。
到目前为止,没有人能够给出一个完全可行、生产就绪的答案。
如何在SwiftUI中创建包含导航的可重用视图?
由于SwiftUI的NavigationLink与View强烈绑定,因此以这种方式创建的可重用视图在大型应用程序中无法扩展。在那些小样本应用程序中,NavigationLink可以工作,但是一旦你想要在一个应用程序中重复使用多个视图,甚至跨越模块边界进行重用(例如:在iOS、WatchOS等中重用视图),NavigationLink就无法胜任了。
设计问题:NavigationLinks已经硬编码到了View中。
NavigationLink(destination: MyCustomView(item: item))
但是,如果包含此NavigationLink
的视图应该是可重用的,我无法硬编码目标。必须有一种机制提供目标。我在这里问过,得到了相当不错的答案,但仍然没有完整的答案:
SwiftUI MVVM Coordinator/Router/NavigationLink
想法是将目标链接注入可重用视图中。通常想法可以工作,但不幸的是,这不能扩展到真正的生产应用程序。一旦我拥有多个可重用屏幕,我就会遇到逻辑问题,即一个可重用视图(ViewA
)需要预配置的视图目标(ViewB
)。但是,如果也需要预配置的视图目标,我需要在中创建,使已经注入之前,我注入到中。等等...但由于此时必须传递的数据不可用,整个构造失败。
我另外一个想法是使用环境作为依赖注入机制来注入NavigationLink
的目标。但我认为这应该被认为是一种hack,而不是大型应用程序的可伸缩解决方案。我们最终会基本上使用环境来处理所有事情。但由于环境也只能在视图中使用(不能在单独的协调器或视图模型中使用),这又会创建奇怪的构造。
像业务逻辑(e.g.视图模型代码)和视图必须分开,导航和视图也必须分开(e.g.协调器模式) 在UIKit
中,这是可能的,因为我们可以访问视图后面的UIViewController
和UINavigationController
。UIKit's
MVC已经存在的问题是,它将许多概念混杂在一起,变成了有趣的名字"Massive-View-Controller"而不是"Model-View-Controller"。现在,在SwiftUI
中出现了类似的问题,但我认为更糟。导航和视图耦合度很高,无法解耦。因此,如果它们包含导航,则无法重用视图。在UIKit
中可以解决此问题,但现在我看不到在SwiftUI
中的明智解决方案。不幸的是,Apple没有向我们提供有关如何解决此类架构问题的解释。我们只得到了一些小型示例应用程序。
我希望被证明是错误的。请向我展示一个干净的应用程序设计模式,可以解决大型生产就绪应用程序的这个问题。
更新:此奖励将在几分钟内结束,不幸的是,仍然没有人能够提供工作示例。但是,如果我找不到其他解决方案并链接到这里,则将开始新的赏金来解决此问题。感谢所有人的贡献!
更新2020年6月18日: 我从苹果获得了有关此问题的答案,建议类似以下内容以解耦视图和模型:
enum Destination {
case viewA
case viewB
case viewC
}
struct Thing: Identifiable {
var title: String
var destination: Destination
// … other stuff omitted …
}
struct ContentView {
var things: [Thing]
var body: some View {
List(things) {
NavigationLink($0.title, destination: destination(for: $0))
}
}
@ViewBuilder
func destination(for thing: Thing) -> some View {
switch thing.destination {
case .viewA:
return ViewA(thing)
case .viewB:
return ViewB(thing)
case .viewC:
return ViewC(thing)
}
}
}
我的回应如下:
感谢您的反馈。但是,正如您所看到的,视图中仍然存在强耦合。现在,“ContentView”需要知道它可以导航到所有视图(ViewA、ViewB、ViewC)。正如我所说的,在小型示例应用程序中可以工作,但它不适用于大型生产就绪的应用程序。
想象一下,在GitHub项目中创建了一个自定义视图。然后在我的应用程序中导入此视图。这个自定义视图不知道它可以导航到其他视图,因为它们是特定于我的应用程序的。
希望我解释得更清楚了。
我认为唯一干净的解决方案是像UIKit那样将导航和视图分离。(例如UINavigationController)
谢谢,Darko
所以目前还没有干净且可行的解决方案。期待2020年的WWDC。
2021年9月更新:
使用 AnyView
并不是解决这个问题的好方法。在大型应用程序中,基本上所有的视图都必须以可重用的方式设计。这意味着 AnyView
将被“无处不在”地使用。我和两位Apple开发人员会面,他们明确向我解释了 AnyView
的性能要比 View 差很多,它应该只在特殊情况下使用。其根本原因是,在编译时无法解析 AnyView
的类型,因此必须在堆上分配。
2022年6月更新:
今天在WWDC上,苹果推出了新的 SwiftUI NavigationStack
。
https://developer.apple.com/documentation/swiftui/navigationstack/
NavigationStack
允许通过使用 .navigationDestination
修饰符将目标视图与当前可见视图分离。这终于是一个干净的协调器方式。
感谢 @Apple 的倾听!