前回の記事で、私が取り組んでいる複数のアプリケーションの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がある場合、次の手順が実行されます。
ClassFinderはProductViewControllerを見つけられず、ルーターは移動しますNilFinderは何も見つけられず、ルーターは移動しますGeneralStep.currentは、常にスタックの一番上のUIViewControllerを返します。- 見つかった
UIViewController起動すると、ルーターは元に戻ります - `NavigationControllerFactoryを使用して
UINavigationControllerを構築します GeneralAction.presentModallyを使用してモーダルで表示します- ProductViewControllerFactory
ProductViewControllerFactory ProductViewController ProductViewController ProductViewControllerFactory ProductViewController ProductViewControllerFactory UINavigationControllerを使用して、作成されたProductViewControllerを以前のUINavigationControllerしUINavigationController.pushToNavigation- ナビゲーションを終了
注: 実際には、 UINavigationControllerせずにUINavigationControllerモーダルに表示することは不可能であることを理解する必要があります。 したがって、手順5〜8は、ルーターによってわずかに異なる順序で実行されます。 しかし、あなたはそれについて考えるべきではありません。 構成について順次説明します。
構成を記述する際の良い習慣は、ユーザーがその時点でアプリケーションのどこにいても、突然、ユーザーが説明している画面に移動するように求めるプッシュメッセージを受信し、質問に答えようとすることです。 ? "、"説明している構成でFinderどのように動作しますか? "。 これらすべての問題を考慮に入れると、ユーザーがどこにいても目的の画面を表示することが保証された構成が得られます。 そして、これは、マーケティングとユーザーの誘致(エンゲージ)に関わるチームの最新のアプリケーションの主な要件です。
StackIteratingFinderとそのオプション:
Finder概念は、最も受け入れやすいと思われる方法で実装できます。 ただし、最も簡単な方法は、画面上のView Controllerのグラフを反復処理することです。 この目標を簡素化するために、ライブラリはStackIteratingFinderと、このタスクを実行するさまざまな実装を提供します。 あなたはただ質問に答える必要があります-これはあなたが期待するUIViewControllerです。
StackIteratingFinderの動作に影響を与え、 StackIteratingFinderのグラフ(スタック) StackIteratingFinder部分で検索するかをSearchOptionsするために、作成時にSearchOptions組み合わせを指定できます。 そして、彼らはより詳細に住むべきです:
current :スタックの一番上のView Controller。 ( rootViewController 、または最上部にモーダルで表示されるもの)visible : UIViewControllerがコンテナーである場合、その可視UIViewController (たとえば、 UISplitControllerは常に1つの可視UIViewController 、 UISplitControllerには表示方法に応じて1つまたは2つがあります)。contained : UIViewControllerがコンテナーである場合、ネストされたすべてのUIViewController検索します(たとえば、表示されているUINavigationControllerを含むUINavigationControllerすべてのビューコントローラーを経由します)presenting :すべてのUIViewController ahのUIViewController下にも検索します(もちろんある場合)presented :提供されたUIViewControllerを検索します( UIViewController場合、このオプションは常に上から始まるため、意味がありません)
次の図により、上記の説明がより明確になります。

前の記事でコンテナの概念を理解することをお勧めします。
例 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))
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()
ユーザーがどこにいても、別のタブまたはある種のモーダルウィンドウでも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つの方法があります。
- [.current、.visible]を使用して、表示されているもののみを検索するように
StackIteratingFinderを設定します NilFinderを使用しNilFinderこれは、ルーターがBagViewController BagViewControllerを見つけられず、常に作成することを意味します。 ただし、このアプローチには副作用があります-たとえば、すでにBagViewControllerいるユーザーがモーダルで表示され、たとえばBagViewControllerが表示するユニバーサルリンクをクリックすると、ルーターはそれを見つけず、別のインスタンスを作成してその上に表示しますモーダル。 これはあなたが望むものではないかもしれません。- モーダルで表示される
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を伴うすべてのアーキテクチャパターンに適合します。
ご意見やご提案をお待ちしております。 特に、いくつかの側面をより詳細に説明する価値があると思われる場合。 おそらく、コンテキストの概念を明確にする必要があります。