Coordinator
模式
2015 年,Soroush Khanlou 就向 Apple 社区介绍了 Coordinator
模式这一概念。简而言之,这一模式将传统场景下由 UIViewController 承担的导航职责抽离为单独的部分,来避免 ViewController 的过度膨胀,并促进 ViewController 的重用。
我们来看一下下面这段代码:
|
|
在上面的代码中,我们可以发现有两处耦合。
TableView
与DetailViewController
TableView
与UINavigationController
让我们逐项分析。第一,TableView
并不需要知道 DetailViewController
的具体类型。如果在这里确定了 DetailViewController
,那么当 TableView
复用并需要推出其他的视图控制器时,处理起来会变的棘手。抽象出基类并针对不同场景创建子类并重写方法?这并不是一个好的选择。
第二,TableView
其实也并不需要知道是谁负责导航。试想一下这样的场景:如果 TableView
作为一个 UISplitViewController
的 Primary Column,那么它的职责是在用点击相应的行时,在 Secondary Column 中推出细节视图控制器,这里的代码同样需要修改。
如何解决以上的问题呢?我想可以抽离出导航部分的代码,创建为 Coordinator
。这样,在不同场景下,我们只需要创建相应的 Coordinator
实例,就可以因地制宜的导航。
SwiftUI 与 UIKit 混合
随着 iOS 的不断发展,SwiftUI 逐渐在开发中被使用,Coordinator
在处理 SwiftUI 与 UIKit 混合时的导航,同样有着优势。
试想这样一个场景,我们需要在 A 视图控制器中,推出由 UIHostingController
包装的 SwiftUI B 视图,又需要在 B 视图中,推出 C 视图控制器。
看起来,可以有两种解决方案,在 B 视图中直接使用 NavigationLink 推出由 UIViewRepresentable
封装的 C 视图控制器,或者向 B 视图中传入 navigationController
。
经过实验,我们可以发现,可以直接在 B 视图中使用 NavigationLink
导航到 C 视图控制器,并且包裹 A 视图控制器的 UINavigationController
会自动作为 NavigationView
/ NavigationStack
与 NavigationLink
配合生效。看起来 Apple 默默做的很好,但一旦事情变得复杂起来,例如需要 B 视图在 Secondary Column 中推出 C 视图控制器时,以上的两种方案均有问题。
方案一中,我们无法找到 showDetailViewController
在 SwiftUI 中的同等声明方式,因此无法实现相应的效果。而在方案二中,我们需要传入 splitViewController
,这导致视图变得更加复杂。
Coordinator
模式应对混合
那么,不妨使用 Coordinator
模式来处理上述问题。在这里,我们用 router 表示 SwiftUI视图 / UIKit视图控制器持有的 Coordinator 实例。
我们在 SwiftUI 视图或者 UIKit 视图控制器创建时注入一个 router 实例,并在需要导航时,调用 router 的方法来处理导航。
这样,在不同的使用场景下,我们可以给 SwiftUI 视图或者 UIKit 视图控制器注入不同的 router,来实现不同的导航;视图控制器也不需要考虑具体的导航方式和下一个视图,只需提供数据即可;SwiftUI 视图也只需要使用 router 来处理一切即可。