現時点では、大規模なプロジェクトの画面間で適切に設計されたナビゲーション(以降、 ルーティング )が非常に重要なタスクになると考えているかもしれません。その解決策は、プロジェクトに参加するすべての人の時間と神経を節約するのに役立ちます
この出版物のルーティングとはどういう意味ですか?
一般的に、これはある画面から別の画面へのパスとして説明できます。 そして、それぞれに独自の方法があります。 誰かがすぐにStoryboard Segueを紹介しますが、誰かがこの挑戦を好きになるでしょう:
self.navigationController?.pushViewController(UIViewController(), animated: true)
どの時点でルーティングレイヤーを再考する必要が生じる可能性がありますか?
- 古いプロジェクト(おそらく自分のプロジェクト)を開いたので、画面間の遷移の全体像を理解できません。
- 大規模なプロジェクトに取り組んでおり、すべてのプロジェクト参加者がすぐにアクセスできるようにし、透過的にしたいと考えています。
- あなたはVIPERについての記事を読んでいて、
素晴らしい建築の議論の世界に没頭することを計画しています。 - ストーリーボードが非常に大きくなったため、新しい画面を追加するたびにさまざまな問題が発生します。
- 低出力のマシンでは、 ストーリーボードプロジェクトを開くことができません。
- ストーリーボードをまったく使用しません( この理由から? )そして、 pushViewControllerのような呼び出しはプロジェクト全体に散らばっています。
- あなたのユニークなケースと他の状況。
ルーティングレイヤーの再考はどこから始められますか?
何がルーティングに入るかを決定することは重要です。 これらは、 pushViewController 、 popViewController 、 popToViewController 、 popToRootViewController 、 present 、 dismiss 、 setViewControllersなどのさまざまな遷移を実行するUIViewController 、 UINavigationControllerなどの関数です。 また、 ルーティングでは、 アラート 、 アクションシート 、 トースト 、 スナックバーなどのさまざまなポップアップを表示できます。
同様に重要なステップは、トランジションの呼び出し方法を決定することです。 理想的には、移行コードは非常に簡潔で、初めて見た人でも理解できるものです。
func someFunction() { ... routing(with: .dismiss) }
遷移のためのUIViewControllerの準備
上記の例を実装しようとすると、 ルーティングはUIViewController自体が実装する関数になります。
extension UIViewController { func routing(with routing: Routing) { ... } }
次に、パラメーターとして関数に分類されるルーティング要素を作成する必要があります。 迅速に、列挙は非常に柔軟であることが判明し、この状況では最適です:
enum Routing { case dismiss case preparedNavigation case selectedCityTransport(CityTransport) case selectedTrafficRoute(TrafficRoute) ... }
同じルーティングパラメーターを異なるUIViewControllerから呼び出すことができ、たとえば、そのような呼び出し中に異なる呼び出しが発生することに注意してください。 したがって、アプリケーションは遷移がどの特定のUIViewControllerから呼び出されたかを知る必要があります。 UIViewControllerとトランジションの適切なマッピングをさまざまな方法で実現できます。 ただし、理想的には、 UIViewControllerにこれらの目的のための特定のパラメーターを持たせたいと思います。 たとえば、
extension UIViewController { enum ViewType { case undefined case navigation case transport ... } private struct Keys { static var key = "\(#file)+\(#line)" } var type: ViewType { get { return objc_getAssociatedObject(self, &Keys.key) as? ViewType ?? .undefined } set { objc_setAssociatedObject(self, &Keys.key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } }
UIViewControllerに新しい型パラメーターを導入することにより、アプリケーション画面間のすべての遷移呼び出しを収集し、呼び出し元のUIViewControllerの型で分類されるルーティング関数を詳細化できます。
extension UIViewController { func routing(with routing: Routing) { switch type { case .navigation: preparedNavigation(with: routing) case .transport: selectedCityTransport(with: routing) default: break } } }
たとえば、各遷移の特定の実装は、個別のプライベートUIViewController拡張に移動できます。
private extension UIViewController { func preparedNavigation(with routing: Routing) { switch routing { case .preparedNavigation: guard let view = self as? UINavigationController else { break } view.setViewControllers([TransportView()], animated: true) default: break } } func selectedCityTransport(with routing: Routing) { switch routing { case .selectedCityTransport(let object): navigationController?.pushViewController(RoutesView(object), animated: true) default: break } } }
最終的に何を達成しましたか?
- すべてのアプリケーションの移行は1か所で記述され、合理化されており、複製されません。
- 移行コールは簡潔で使いやすいです。
- 必要に応じて、何らかの理由でStoryboardの使用を安全に拒否できます。 たとえば、 AsyncDisplayKitを使用することにしました 。
- 新しいマネージャー、サービス、またはシングルトーンは登場しませんでした...すべてのロジックはUIViewController拡張内に残ります。
このアプローチは、出版物の著者によって使用されましたか?
最初から迅速に記述された2つのプロジェクトでは、 ルーティングレイヤーの提示された実装を実装することができました。 プロジェクトの1つはRxSwiftを使用して作成され、 ルーティングコールは次のようにラップされました。
extension Reactive where Base: UIViewController { var observerRouting: AnyObserver<Routing> { let binding = UIBindingObserver(UIElement: base) { (view: UIViewController, routing: Routing) in view.routing(with: routing) } return binding.asObserver() } }
プロジェクトサイズは55kおよび125k locでした。 ルーティングレイヤー全体を含む各プロジェクトのファイルサイズはほぼ同じで、約600行のコードになりました。