Konfigurationsbeispiele für UIViewControllers mit RouteComposer

    Im vorigen Artikel habe ich über den Ansatz gesprochen, den wir verwenden, um Komposition und Navigation zwischen Ansichts-Controllern in verschiedenen Anwendungen, an denen ich arbeite, zu implementieren, was letztendlich zu einer separaten Bibliothek RouteComposer führte . Ich erhielt eine beträchtliche Anzahl angenehmer Antworten auf den vorherigen Artikel und einige praktische Ratschläge, die mich dazu veranlassten, einen weiteren zu schreiben, der die Konfiguration der Bibliothek ein wenig näher erläutern würde. Unter dem Schnitt werde ich versuchen, einige der häufigsten Konfigurationen zu zerlegen.



    Wie der Router die Konfiguration analysiert


    Überlegen Sie sich zunächst, wie der Router die von Ihnen geschriebene Konfiguration analysiert. Nehmen Sie das Beispiel aus dem vorherigen Artikel:


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

    Der Router durchläuft die Kette der Schritte ab dem ersten Schritt, bis einer der Schritte (mit dem bereitgestellten Finder) "informiert", dass der gewünschte Schritt UIViewControllerbereits auf dem Stapel ist. (Es ist zum Beispiel GeneralStep.current()garantiert, dass es im Stack von View-Controllern vorhanden ist.) Dann beginnt der Router wieder entlang der Kette von Schritten, erstellt die erforderlichen UIViewControllermit den bereitgestellten Fabricund integriert sie mit den angegebenen Action. Dank der Typprüfung zur Kompilierzeit können Sie in der Regel nicht Actioninkompatible s verwenden, d. H. FabricSie können keinen UITabBarController.addTabeingebauten View-Controller verwenden NavigationControllerFactory.


    Wenn Sie sich die oben beschriebene Konfiguration vorstellen, werden dann, wenn Sie ProductViewControlleretwas nicht auf dem Bildschirm sehen , die folgenden Schritte ausgeführt:


    1. ClassFinderwird nicht finden ProductViewControllerund der Router bewegt sich weiter
    2. NilFinder findet nie etwas und der Router bewegt sich weiter
    3. GeneralStep.currentwird immer den obersten UIViewControllerim Stapel zurückgeben.
    4. Nach UIViewControllerdem Start wird der Router zurückgeschaltet
    5. Erstellen Sie UINavigationControllermit `NavigationControllerFactory
    6. Wird es modal anzeigen GeneralAction.presentModally
    7. ProductViewControllerWird mit erstellenProductViewControllerFactory
    8. Integriert ProductViewControllerin der vorherigen UINavigationControlleripolzuya erstelltUINavigationController.pushToNavigation
    9. Beenden Sie die Navigation

    NB: Es sollte verstanden werden, dass es in Wirklichkeit nicht UINavigationControllerohne irgendeine Art modal dargestellt werden UIViewControllerkann. Daher werden die Schritte 5-8 vom Router in einer etwas anderen Reihenfolge ausgeführt. Aber daran darf nicht gedacht werden. Die Konfiguration wird sequentiell beschrieben.


    Beim Schreiben einer Konfiguration empfiehlt sich die Annahme, dass sich der Benutzer momentan an einer beliebigen Stelle in Ihrer Anwendung befinden kann und plötzlich eine Push-Nachricht erhält, in der Sie aufgefordert werden, zu dem von Ihnen beschriebenen Bildschirm zu gelangen und die Frage zu beantworten - "Wie soll sich eine Anwendung verhalten?" ? "," Wie werden Sie sich Finderin der von mir beschriebenen Konfiguration verhalten ? ". Wenn all diese Fragen berücksichtigt werden, erhalten Sie eine Konfiguration, die dem Benutzer garantiert den gewünschten Bildschirm anzeigt, wo er sich gerade befindet. Dies ist die Hauptanforderung an moderne Anwendungen von den Teams, die mit dem Marketing und der Gewinnung (gewinkelter) Benutzer befasst sind.


    StackIteratingFinder und ihre Optionen:


    Sie können das Konzept Finderauf jede Art und Weise implementieren, die Sie für am geeignetsten halten. Am einfachsten ist es jedoch, das Diagramm der Ansichtssteuerungen auf dem Bildschirm zu durchlaufen. Um dieses Ziel zu vereinfachen, stellt die Bibliothek StackIteratingFinderverschiedene Implementierungen bereit , die diese Aufgabe übernehmen. Sie müssen nur die Frage beantworten - UIViewControllererwartet Sie diese Frage?


    Um das Verhalten zu beeinflussen StackIteratingFinderund zu bestimmen, in welchen Teilen des Diagramms (Stapels) ich die Controller verdrehe, nach denen Sie suchen möchten, können Sie beim Erstellen eine Kombination angeben SearchOptions. Und sie sollten genauer besprochen werden:


    • current: Der Controller der obersten Ansicht im Stapel. (Das, das ist rootViewControllerin UIWindow, oder derjenige, der modal oben gezeigt ist)
    • visible: Falls es sich UIViewControllerum einen Container handelt, suchen Sie nach dem sichtbaren UIViewControllerah (Zum Beispiel: Sie haben UINavigationControllerimmer einen sichtbaren UIViewController, UISplitControllersie können einen oder zwei haben, je nachdem, wie der Container dargestellt wird.)
    • contained: Für den Fall, dass es sich UIViewControllerum einen Container handelt, schauen Sie in alle verschachtelten ahs UIViewController(Beispiel: Durchlaufen Sie alle Ansichtscontroller UINavigationControllereinschließlich des sichtbaren Controllers ).
    • presenting: Suche auch in allen UIViewControllerah unter der obersten (wenn es natürlich gibt)
    • presented: Suche in UIViewControllerah über die zur Verfügung gestellte (für StackIteratingFinderdiese Option ist dies nicht sinnvoll, da sie immer von oben beginnt)

    Die folgende Abbildung kann die obige Erklärung visueller machen:


    Ich würde empfehlen, sich mit dem Konzept der Container im vorherigen Artikel vertraut zu machen.


    Beispiel Wenn Sie Ihre FinderSuche AccountViewControllerim gesamten Stack durchführen möchten , aber nur unter den sichtbaren, UIViewControllersollte dies wie folgt geschrieben werden:


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

    Hinweis: Wenn aus wenigen Gründen nur wenige Einstellungen zur Verfügung stehen, können Sie leicht Ihre eigene Implementierung schreiben Finder. Ein Beispiel wird in diesem Artikel sein.


    Gehen wir weiter zu Beispielen.


    Konfigurationsbeispiele mit Erklärungen


    Ich habe UIViewControllereine rootViewControllerOm UIWindow, die nach dem Ende der Navigation durch eine bestimmte ersetzt wird HomeViewController:


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

    XibFactorywird HomeViewControlleraus der xib-Datei geladenHomeViewController.xib


    Denken Sie daran, dass Sie bei abstrakten Implementierungen Finderund Factoryin Kombination den Typ UIViewControllerund den Kontext mindestens einer der Entitäten angeben müssenClassFinder<HomeViewController, Any?>


    Was passiert, wenn ich im obigen Beispiel GeneralStep.rootmit ersetze GeneralStep.current?


    Die Konfiguration funktioniert solange, bis sie zu dem Zeitpunkt aufgerufen wird, zu dem Modalitäten auf dem Bildschirm angezeigt werden UIViewController. In diesem Fall kann der GeneralAction.replaceRootRoot-Controller nicht ersetzt werden, da sich darüber ein modaler Controller befindet und der Router einen Fehler meldet. Wenn diese Konfiguration auf jeden Fall funktionieren soll, müssen Sie dem Router erklären, dass er GeneralAction.replaceRootauf den Root- Router angewendet werden soll UIViewController. Dann entfernt der Router alle modal präsentierten UIViewControllers und die Konfiguration funktioniert auf jeden Fall.


    Ich möchte einiges zeigen AccountViewController, wenn es immer noch gut gezeigt wird, UINavigationControllerund das derzeit irgendwo auf dem Bildschirm angezeigt wird (auch wenn dieses UINavigationControllerunter einem bestimmten UIViewControllerModalom ist):


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

    Was bedeutet diese Konfiguration NilFactory? Auf diese Weise sagen Sie dem Router, dass er, wenn er keinen UINavigationControllerauf dem Bildschirm finden kann, nicht möchte, dass er ihn erstellt, und dass er in diesem Fall einfach nichts getan hat. By the way, da dies NilFactoryist - Sie können nicht danach verwenden Action.


    Ich möchte eine bestimmte zeigen AccountViewController, falls sie noch nicht angezeigt wird, in einer beliebigen Person UINavigationControllerund wer sich gerade auf dem Bildschirm befindet, und wenn es UINavigationControllerkeine Person gibt , erstellen Sie sie und zeigen Sie sie modal an:


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

    Ich möchte UITabBarControllermit Registerkarten anzeigen, die diese enthalten HomeViewControllerund AccountViewControllerdurch den aktuellen Stamm ersetzen:


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

    Kann ich benutzerdefinierte UIViewControllerTransitioningDelegatemit Aktion verwenden GeneralAction.presentModally:


    let transitionController = CustomViewControllerTransitioningDelegate()
    // Где нужно в конфигурации
    .using(GeneralAction.PresentModally(transitioningDelegate: transitionController))

    Ich möchte dorthin AccountViewController, wo sich der Benutzer befindet, in einem anderen Tab oder sogar in einem modalen Fenster:


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

    Warum verwenden wir hier NilFactory? Wir müssen nicht bauen, AccountViewControllerfalls es nicht gefunden wird. Es wird in der Konfiguration eingebaut tabScreen. Sieh sie oben.


    Ich möchte modal zeigen ForgotPasswordViewController, aber natürlich nach LoginViewControllereinem Insider UINavigationControllera:


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

    Sie können die Konfiguration im Beispiel für die Navigation und in ForgotPasswordViewControllerund in verwendenLoginViewController


    Warum expectingContainerim obigen Beispiel?


    Da für die Aktion ein a in der Konfiguration pushToNavigationerforderlich ist UINavigationController, können mit der Methode expectingContainerKompilierungsfehler vermieden werden, sodass sichergestellt ist, dass der Router die loginScreenLaufzeit erreicht, sobald er UINavigationControllervorhanden ist.


    Was passiert, wenn ich in der obigen Konfiguration GeneralStep.currentmit ersetze GeneralStep.root?


    Es wird funktionieren, aber wie Sie den Router sagen , dass Sie zu wollen, begann er eine Kette von rue zu bauen UIViewController, dass , wenn es eine modalen ist offen sein wird , auf es UIViewControllerist, der Router wird sie verbergen , bevor Sie eine Kette zu bauen beginnen.


    In meiner Anwendung gibt es ein UITabBarControllerumfassendes HomeViewControllerund BagViewControllerals Tabs. Ich möchte, dass der Benutzer wie üblich mit den Symbolen auf den Registerkarten zwischen ihnen wechselt. Wenn ich die Konfiguration jedoch programmgesteuert aufrufe (z. B. klickt der Benutzer "Go to Bag" im Inneren HomeViewController), sollte die Anwendung die Registerkarte nicht wechseln, sondern BagViewControllermodal anzeigen.


    Hier sind 3 Möglichkeiten, dies in der Konfiguration zu erreichen:


    1. Nastray- StackIteratingFinderSuche nur sichtbar mit [.current, .visible]
    2. Verwenden Sie NilFinderdie , dass der Router nie in Tabah verfügbaren Plätze bedeuten würde , BagViewControllerund wird es immer schaffen. Dieser Ansatz hat jedoch einen Nebeneffekt: Wenn beispielsweise ein Benutzer in einem BagViewControllere bereits modalisiert ist und beispielsweise auf einen universellen Link geklickt wird, der ihn anzeigen soll BagViewController, wird der Router ihn nicht finden, eine andere Instanz erstellen und ihn modal anzeigen. Dies ist möglicherweise nicht das, was Sie wollen.
    3. Ändern Sie ein bisschen, ClassFinderso dass nur BagViewControllerdas angezeigte Modal gefunden wird und der Rest ignoriert wird, und verwenden Sie es bereits in der Konfiguration.

    structModalBagFinder: StackIteratingFinder{
        funcisTarget(_ 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()

    Anstelle des Schlusses


    Ich hoffe, dass die Konfigurationsmöglichkeiten des Routers etwas klarer geworden sind. Wie ich bereits gesagt habe, verwenden wir diesen Ansatz in drei Anwendungen und sind noch nicht auf eine Situation gestoßen, in der er nicht flexibel genug wäre. Die Bibliothek sowie die Implementierung des bereitgestellten Routers verwenden keine objektiven Tricks und folgen vollständig allen Konzepten von Cocoa Touch. Sie helfen nur, den Kompositionsprozess in Schritte zu unterteilen und führen sie in einer bestimmten Reihenfolge aus und werden mit den iOS-Versionen 9 bis 12 getestet Dieser Ansatz passt in alle Architekturmuster, die das Arbeiten mit einem UIViewControllerStapel erfordern (MVC, MVVM, VIP, RIB, VIPER usw.).


    Ich freue mich über Ihre Kommentare und Vorschläge. Vor allem, wenn Sie der Meinung sind, dass einige Aspekte genauer besprochen werden sollten. Vielleicht muss der Begriff Kontext geklärt werden.


    Jetzt auch beliebt: