前回の記事で、私が取り組んでいる複数のアプリケーションの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
ProductViewControllerProductViewControllerFactory
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)) // - .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つの方法があります。
- [.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
を伴うすべてのアーキテクチャパターンに適合します。
ご意見やご提案をお待ちしております。 特に、いくつかの側面をより詳細に説明する価値があると思われる場合。 おそらく、コンテキストの概念を明確にする必要があります。