RouteComposerを使用したUIViewControllersの構成例

前回の記事で、私が取り組んでいる複数のアプリケーションのView Controller間で構成およびナビゲートするために使用するアプローチについて説明しました。その結果、個別の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()
      
      





ルーターは、最初のステップから( UIViewController



Finder



を使用して)目的のUIViewController



既にスタックにあることを通知するまで、一連のステップを実行します。 (たとえば、 GeneralStep.current()



コントローラービュースタックに存在すること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 ProductViewControllerFactory



    ProductViewController



    ProductViewController ProductViewControllerFactory



    ProductViewController



    ProductViewControllerFactory



  8. UINavigationController



    を使用して、作成されたProductViewController



    を以前のUINavigationController



    UINavigationController.pushToNavigation



  9. ナビゲーションを終了


注: 実際には、 UINavigationController



せずにUINavigationController



モーダルに表示することは不可能であることを理解する必要があります。
したがって、手順5〜8は、ルーターによってわずかに異なる順序で実行されます。 しかし、あなたはそれについて考えるべきではありません。 構成について順次説明します。







構成を記述する際の良い習慣は、ユーザーがその時点でアプリケーションのどこにいても、突然、ユーザーが説明している画面に移動するように求めるプッシュメッセージを受信し、質問に答えようとすることです。 ? "、"説明している構成でFinder



どのように動作しますか? "。 これらすべての問題を考慮に入れると、ユーザーがどこにいても目的の画面を表示することが保証された構成が得られます。 そして、これは、マーケティングとユーザーの誘致(エンゲージ)に関わるチームの最新のアプリケーションの主な要件です。







StackIteratingFinder



とそのオプション:



Finder



概念は、最も受け入れやすいと思われる方法で実装できます。 ただし、最も簡単な方法は、画面上のView Controllerのグラフを反復処理することです。 この目標を簡素化するために、ライブラリはStackIteratingFinder



と、このタスクを実行するさまざまな実装を提供します。 あなたはただ質問に答える必要があります-これはあなたが期待するUIViewController



です。







StackIteratingFinder



の動作に影響を与え、 StackIteratingFinder



のグラフ(スタック) StackIteratingFinder



部分で検索するかをSearchOptions



するために、作成時にSearchOptions



組み合わせを指定できます。 そして、彼らはより詳細に住むべきです:









次の図により、上記の説明がより明確になります。







前の記事でコンテナの概念を理解することをお勧めします。







Finder



スタック全体でAccountViewController



Finder



検索し、表示されているAccountViewController



のみFinder



検索Finder



ようにするには、次のように記述します。







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





NB 何らかの理由で提供されている設定が少ない場合Finder



実装をいつでも簡単に記述できます。
1つの例がこの記事にあります。







実際、例に渡しましょう。







説明付きの構成の例



rootViewController



UIWindow



である特定のHomeViewController



があり、ナビゲーションの最後に特定のHomeViewController



置き換えたいです:



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





XibFactory



、HomeViewController.xibのxibファイルからXibFactory



ロードしますHomeViewController.xib









Finder



Factory



抽象実装を組み合わせて使用​​する場合、少なくとも1つのエンティティ( ClassFinder<HomeViewController, Any?>



UIViewController



とコンテキストのタイプを指定する必要があることを忘れないでください







上記の例でGeneralStep.root



GeneralStep.current



に置き換えたらどうGeneralStep.current



ますか?



画面上にモーダルUIViewController



があるときに呼び出されるまで、構成は機能します。 この場合、 GeneralAction.replaceRoot



はルートコントローラーを置き換えることができません。これは、その上にモーダルコントローラーがあり、ルーターがエラーを報告するためです。 とにかくこの構成を機能させるには、ルーターにGeneralAction.replaceRoot



特にルートUIViewController



適用GeneralAction.replaceRoot



ことを説明する必要があります。 その後、ルーターは、モーダルで表されたすべてのUIViewController



を削除し、どのような状況でも構成が機能します。







AccountViewController



内で現在も画面上にある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



NilFactory



意味ですか? これにより、画面上でUINavigationController



が見つからなかった場合、ルーターで作成せず、この場合は何もしないことをルーターに伝えます。 ちなみに、これはNilFactory



、その後にAction



を使用することはできません。







AccountViewController



内にまだ表示されていない場合、現在画面のどこかにあるUINavigationController



を表示し、そのようなUINavigationController



がない場合は、それを作成してモーダルに表示します。



 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()
      
      





HomeViewController



AccountViewController



を含むUITabBarController



UITabBarController



、現在のUITabBarController



をそれに置き換え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()
      
      





GeneralAction.presentModally



アクションでカスタムUIViewControllerTransitioningDelegate



を使用できますか?



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





ユーザーがどこにいても、別のタブまたはある種のモーダルウィンドウでもAccountViewController



にアクセスしたい:



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





NilFactory



を使用しているのはなぜですか?
AccountViewController



が見つからない場合は、構築する必要はありません。
tabScreen



構成に組み込まれます。
彼女をご覧ください。







ForgotPasswordViewController



モーダルに表示したいのですが、 LoginViewController



内のUINavigationController



後:



 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()
      
      





ForgotPasswordViewController



LoginViewController



ForgotPasswordViewController



でのナビゲーションに、例の構成を使用できます。







上記の例でなぜexpectingContainer



が必要ですか?



pushToNavigation



アクションにはpushToNavigation



の存在が必要であり、その後の構成にあるため、 expectingContainer



メソッドを使用すると、 loginScreen



ルーターがloginScreen



に到達したときにloginScreen



が存在するように注意することでコンパイルエラーを回避できます。







上記の構成でGeneralStep.current



GeneralStep.root



置き換えるとどうなりますか?



動作しますが、ルートUIViewController



からチェーンの構築を開始することをルーターに指示するため、モーダルUIViewController



がその上で開かれている場合、ルーターはチェーンの構築を開始する前にそれらを非表示にします。







私のアプリケーションには、 UITabBarController



BagViewController



をタブとして含むHomeViewController



あります。 ユーザーが通常どおりタブのアイコンを使用してそれらを切り替えることができるようにしたいと思います。 ただし、プログラムで構成を呼び出す場合(たとえば、ユーザーがHomeViewController



内のHomeViewController



バッグに移動]をクリックするHomeViewController



)、アプリケーションはタブを切り替えず、 BagViewController



モーダルに表示するBagViewController



があります。



構成でこれを実現するには、3つの方法があります。







  1. [.current、.visible]を使用して、表示されているもののみを検索するようにStackIteratingFinder



    を設定します
  2. NilFinder



    を使用しNilFinder



    これは、ルーターがBagViewController



    BagViewControllerを見つけられず、常に作成することを意味します。 ただし、このアプローチには副作用があります-たとえば、すでにBagViewController



    いるユーザーがモーダルで表示され、たとえばBagViewController



    が表示するユニバーサルリンクをクリックすると、ルーターはそれを見つけず、別のインスタンスを作成してその上に表示しますモーダル。 これはあなたが望むものではないかもしれません。
  3. モーダルで表示されるBagViewController



    のみを検出し、残りを無視するように、小さなClassFinder



    変更し、構成で既に使用します。


 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など)のUIViewController



を伴うすべてのアーキテクチャパターンに適合します。







ご意見やご提案をお待ちしております。 特に、いくつかの側面をより詳細に説明する価値があると思われる場合。 おそらく、コンテキストの概念を明確にする必要があります。








All Articles