使用RouteComposer的UIViewControllers的配置示例

在上一篇文章中,我讨论了用于在我工作的多个应用程序中的视图控制器之间进行组合和导航的方法,因此,这产生了一个单独的RouteComposer库。 我收到了有关前一篇文章的大量令人愉快的反馈以及一些实用的建议,这促使我写了另一篇文章,对如何配置库进行了进一步解释。 在裁减下,我将尝试找出一些最常见的配置。



路由器如何解析配置


首先,请考虑路由器如何解析您编写的配置。 以上一篇文章为例:


let productScreen = StepAssembly(finder: ClassFinder(options: [.current, .visible]), factory: ProductViewControllerFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() 

路由器将从头开始执行所有步骤,直到其中一个步骤(使用提供的Finder )通知所需的UIViewController已经在堆栈上。 (例如,保证在控制器堆栈中存在GeneralStep.current() 。)然后,路由器将开始沿着步骤链移动,使用提供的UIViewController创建所需的UIViewController并使用指定的Action集成它们。 由于即使在编译阶段也要进行类型检查,大多数情况下,您将无法使用与提供的Fabric不兼容的UITabBarController.addTab (也就是说,您将无法在NavigationControllerFactory构建的UITabBarController.addTab控制器中使用UITabBarController.addTab )。


如果您想象上述配置,那么如果屏幕上只有某个ProductViewController ,将执行以下步骤:


  1. ClassFinder将找不到ProductViewController并且路由器将继续运行
  2. NilFinder永远找不到任何东西,路由器将继续前进
  3. GeneralStep.current将始终返回堆栈上最顶层的UIViewController
  4. 找到启动UIViewController ,路由器将回头
  5. 使用`NavigationControllerFactory构建一个UINavigationController
  6. 将使用GeneralAction.presentModally模态显示
  7. ProductViewControllerFactory ProductViewController ProductViewControllerFactory
  8. 使用UINavigationController.pushToNavigation将创建的ProductViewController集成到以前的UINavigationController
  9. 完成导航

注意: 应该理解,实际上,如果其中没有某些UIViewController ,就不可能模态地显示UINavigationController 因此,路由器将以略有不同的顺序执行步骤5-8。 但是您不应该考虑它。 依次描述该配置。


编写配置时的一个好习惯是假设用户现在可以位于应用程序中的任何位置,并且突然收到一条推送消息,其中包含进入到您正在描述的屏幕的请求,并尝试回答问题-“应用程序应如何表现? “,” Finder在我描述的配置中将如何表现? 如果考虑了所有这些问题,您将获得一个配置,可以确保无论用户身在何处,都可以向其显示所需的屏幕。 这是涉及市场和吸引(吸引)用户的团队对现代应用程序的主要要求。


StackIteratingFinder及其选项:


您可以以您认为最可接受的任何方式实施Finder概念。 但是,最简单的方法是遍历屏幕上视图控制器的图形。 为了简化此目标,该库提供了StackIteratingFinder和执行此任务的各种实现。 您只需要回答问题-这就是您期望的UIViewController


为了影响StackIteratingFinder的行为并告诉控制器希望在图形(堆栈)的哪个部分中查找,可以在创建它时指定SearchOptions的组合。 他们应该详细说明:


  • current :堆栈中最顶层的视图控制器。 (这是UIWindowrootViewController或在最上方以模态显示的那个)
  • visible :如果UIViewController是一个容器,请查看其可见的UIViewController ah(例如: UINavigationController始终有一个可见的UIViewControllerUISplitController其显示方式, UISplitController可能有一个或两个。)
  • contained :如果UIViewController是一个容器,请在其所有嵌套的UIViewController搜索(例如:遍历UINavigationController所有视图控制器,包括可见的视图控制器)
  • presenting :在所有UIViewController啊中也搜索UIViewController (如果有的话)
  • StackIteratingFinder :在UIViewController中搜索提供的选项(对于StackIteratingFinder此选项没有意义,因为它始终从顶部开始)

下图可能使上面的解释更加明显:


我建议您在上一篇文章中熟悉容器的概念。


示例如果您希望Finder在整个堆栈中Finder AccountViewController ,而仅在可见的UIViewController Finder则应这样编写:


 ClassFinder<AccountViewController, Any?>(options: [.current, .visible, .presenting]) 

注意 如果由于某种原因提供的设置很少,您总是可以轻松编写Finder a的实现。 本文将提供一个示例。


实际上,让我们通过示例。


配置示例及说明


我有一个特定的UIViewController ,它是UIWindowrootViewController ,我希望在导航结束时将其替换为某个HomeViewController


 let screen = StepAssembly( finder: ClassFinder<HomeViewController, Any?>(), factory: XibFactory()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble() 

XibFactory HomeViewController的xib文件加载HomeViewController.xib


不要忘记,如果您结合使用FinderFactory抽象实现,则必须为至少一个实体ClassFinder<HomeViewController, Any?>指定UIViewController的类型和上下文。


如果在上面的示例中,我将GeneralStep.root替换为GeneralStep.root ,会发生什么情况?


该配置将一直有效,直到在屏幕上出现任何模式UIViewController时调用它为止。 在这种情况下, GeneralAction.replaceRoot将无法替换根控制器,因为它上面有一个模态控制器,并且路由器将报告错误。 如果您仍然希望此配置正常工作,则需要向路由器说明您希望将GeneralAction.replaceRoot专门应用于根UIViewController 。 然后,路由器将删除所有以模态表示的UIViewController并且该配置将在任何情况下均可用。


我想在任何UINavigationController内显示一些AccountViewController ,如果仍然可以很好地显示,并且它当前在屏幕上的某个位置(即使该UINavigationController在某些模式UIViewController ):


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleStep(ClassFinder<UINavigationController, Any?>(), NilFactory())) .from(GeneralStep.current()) .assemble() 

NilFactory在此配置中是什么意思? 这样,您告诉路由器,如果他在屏幕上找不到任何UINavigationController ,则不希望他创建它,并且在这种情况下什么也不做。 顺便说一下,由于这是NilFactory ,因此您不能在其后使用Action


我想在任何UINavigationController内部显示一个AccountViewController (如果尚未显示),并且当前不在屏幕上,如果不是这样,则创建它并以模态显示:


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.PushToNavigation()) .from(SwitchAssembly<UINavigationController, Any?>() .addCase(expecting: ClassFinder<UINavigationController, Any?>(options: .visible)) //   -    .assemble(default: { //      return ChainAssembly() .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() }) ).assemble() 

我想显示UITabBarController ,其中包含HomeViewControllerAccountViewController UITabBarController用当前根替换:


 let tabScreen = SingleContainerStep( finder: ClassFinder(), factory: CompleteFactoryAssembly(factory: TabBarControllerFactory()) .with(XibFactory<HomeViewController, Any?>(), using: UITabBarController.addTab()) .with(XibFactory<AccountViewController, Any?>(), using: UITabBarController.addTab()) .assemble()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble() 

我可以将UIViewControllerTransitioningDelegateGeneralAction.presentModally操作一起使用:


 let transitionController = CustomViewControllerTransitioningDelegate() //     .using(GeneralAction.PresentModally(transitioningDelegate: transitionController)) 

我想转到AccountViewController ,无论用户在哪里,都在另一个选项卡甚至某种模式的窗口中:


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: NilFactory()) .from(tabScreen) .assemble() 

为什么在NilFactory使用NilFactory 如果找不到,则不需要构建AccountViewController 它将在tabScreen配置中构建。 在上面见她。


我想模态显示ForgotPasswordViewController ,但是,当然要在UINavigationController里面的LoginViewController之后:


 let loginScreen = StepAssembly( finder: ClassFinder<LoginViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() let forgotPasswordScreen = StepAssembly( finder: ClassFinder<ForgotPasswordViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(loginScreen.expectingContainer()) .assemble() 

您可以将示例中的配置用于ForgotPasswordViewControllerLoginViewController导航


为什么在上面的示例中使用expectingContainer


由于pushToNavigation操作要求存在UINavigationController并在其后的配置中,所以expectingContainer方法允许我们确保当路由器在loginScreen到达loginScreenUINavigationController在那里,从而避免了编译错误。


如果在上述配置中将GeneralStep.current替换为GeneralStep.current会发生什么?


它将起作用,但是由于您告诉路由器要从根UIViewController开始构建链,如果在其上方打开了任何模式UIViewController ,则路由器将在开始构建链之前将其隐藏。


我的应用程序有一个UITabBarController其中包含HomeViewControllerBagViewController作为选项卡。 我希望用户能够像往常一样使用选项卡上的图标在它们之间切换。 但是,如果我以编程方式调用配置(例如,用户在HomeViewController单击“转到袋”),则应用程序不应切换选项卡,而应BagViewController模态显示BagViewController


在配置中有3种方法可以实现此目的:


  1. StackIteratingFinder设置为仅使用[.current,.visible]搜索可见的内容
  2. 使用NilFinder意味着路由器将永远不会在BagViewController找到BagViewController并将始终创建它。 但是,这种方法会产生副作用-例如,如果以模态显示了已经在BagViewController过的用户,并且例如单击了BagViewController应该向他显示的通用链接,则路由器将找不到它,而是将创建另一个实例并将其显示在其上方模态地 这可能不是您想要的。
  3. 更改一个小的ClassFinder ,使其仅找到模态显示的BagViewController而忽略其余的内容,并已在配置中使用它。

 struct ModalBagFinder: StackIteratingFinder { func isTarget(_ viewController: BagViewController, with context: Any?) -> Bool { return viewController.presentingViewController != nil } } let screen = StepAssembly( finder: ModalBagFinder(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() 

而不是结论


我希望配置路由器的方法变得更加清晰。 就像我说的那样,我们在3个应用程序中使用了这种方法,但是还没有遇到它不够灵活的情况。 该库以及提供给它的路由器的实现,在运行时不使用任何客观技巧,并且完全遵循所有Cocoa Touch概念,仅有助于将合成过程分解为若干步骤,并按给定顺序执行,并在iOS 9至12版中进行了测试。 ,这种方法适用于涉及使用UIViewController堆栈的所有架构模式(MVC,MVVM,VIP,RIB,VIPER等)


我很高兴您的意见和建议。 特别是如果您认为值得在某些方面进行更详细的介绍。 上下文的概念也许需要澄清。

Source: https://habr.com/ru/post/zh-CN428990/


All Articles