Konfigurationsbeispiele für UIViewController mit RouteComposer

In einem früheren Artikel habe ich über den Ansatz gesprochen, den wir zum Erstellen und Navigieren zwischen Ansichtscontrollern in mehreren Anwendungen verwenden, an denen ich arbeite, was zu einer separaten RouteComposer- Bibliothek führte. Ich erhielt eine beträchtliche Menge angenehmer Rückmeldungen zum vorherigen Artikel und einige praktische Ratschläge, die mich dazu veranlassten, einen weiteren zu schreiben, der etwas mehr über die Konfiguration der Bibliothek erklärt. Unter dem Schnitt werde ich versuchen, einige der häufigsten Konfigurationen zu erkennen.



Wie der Router die Konfiguration analysiert


Überlegen Sie 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 Schrittkette von Anfang an, bis einer der Schritte (mithilfe des mitgelieferten Finder ) „meldet“, dass sich der gewünschte UIViewController bereits auf dem Stapel befindet. (So ​​ist beispielsweise GeneralStep.current() garantiert im Controller-Ansichtsstapel vorhanden.) Anschließend bewegt sich der Router entlang der UIViewController , erstellt die erforderlichen UIViewController mit den bereitgestellten UIViewController und integriert sie mit den angegebenen Action . Dank der Typprüfung können Sie bereits in der Kompilierungsphase häufig keine UITabBarController.addTab , die mit der bereitgestellten Fabric nicht kompatibel sind ( UITabBarController.addTab Sie können UITabBarController.addTab in dem von NavigationControllerFactory UITabBarController.addTab ).


Wenn Sie sich die oben beschriebene Konfiguration vorstellen und nur einen bestimmten ProductViewController auf dem Bildschirm haben, werden die folgenden Schritte ausgeführt:


  1. ProductViewController findet ProductViewController und der Router ProductViewController fort
  2. NilFinder wird niemals etwas finden und der Router wird NilFinder
  3. GeneralStep.current gibt immer den obersten UIViewController auf dem Stapel zurück.
  4. UIViewController , wird der Router zurückgeschaltet
  5. UINavigationController einen UINavigationController mit der NavigationControllerFactory
  6. Zeigt es modal mit GeneralAction.presentModally
  7. ProductViewController ProductViewController ProductViewControllerFactory
  8. Integriert den erstellten UINavigationController mithilfe von UINavigationController.pushToNavigation in den vorherigen UINavigationController.pushToNavigation
  9. Beenden Sie die Navigation

NB: Es versteht sich, dass es in der Realität unmöglich ist, einen UINavigationController ohne einen darin enthaltenen UINavigationController modal UIViewController . Daher werden die Schritte 5 bis 8 vom Router in einer etwas anderen Reihenfolge ausgeführt. Aber du solltest nicht darüber nachdenken. Die Konfiguration wird nacheinander beschrieben.


Wenn Sie eine Konfiguration schreiben, sollten Sie davon ausgehen, dass sich der Benutzer derzeit an einer beliebigen Stelle in Ihrer Anwendung befindet und plötzlich eine Push-Nachricht mit der Aufforderung erhält, zu dem von Ihnen beschriebenen Bildschirm zu gelangen, und versuchen, die Frage zu beantworten: "Wie soll sich die Anwendung verhalten?" ? "," Wie verhält sich der Finder in der von mir beschriebenen Konfiguration? ". Wenn all diese Fragen berücksichtigt werden, erhalten Sie eine Konfiguration, die dem Benutzer garantiert den gewünschten Bildschirm anzeigt, wo immer er sich befindet. Dies ist die Hauptanforderung für moderne Anwendungen der Teams, die mit dem Marketing und der Gewinnung (Einbeziehung) von Benutzern befasst sind.


StackIteratingFinder und seine Optionen:


Sie können das Finder Konzept so implementieren, wie Sie es für am akzeptabelsten halten. Am einfachsten ist es jedoch, das Diagramm der Ansichtssteuerungen auf dem Bildschirm zu durchlaufen. Um dieses Ziel zu vereinfachen, bietet die Bibliothek StackIteratingFinder und verschiedene Implementierungen, die diese Aufgabe übernehmen. Sie müssen nur die Frage beantworten - ist dies der UIViewController , den Sie erwarten.


Um das Verhalten des StackIteratingFinder zu beeinflussen und StackIteratingFinder , in welchen Teilen des Diagramms (Stacks) die Controller suchen sollen, können SearchOptions beim Erstellen eine Kombination von SearchOptions angeben. Und sie sollten genauer darauf eingehen:


  • current : Der oberste Ansichtscontroller auf dem Stapel. ( rootViewController der UIWindow des UIWindow oder derjenige, der ganz oben modal angezeigt wird)
  • visible : Wenn der UIViewController ein Container ist, schauen Sie in seinen sichtbaren UIViewController (Beispiel: UINavigationController immer einen sichtbaren UIViewController , UISplitController kann je nach UISplitController einen oder zwei haben).
  • contained : Falls der UIViewController ein Container ist, suchen Sie in allen verschachtelten UIViewController (Beispiel: Gehen Sie alle View-Controller des UINavigationController einschließlich des sichtbaren).
  • presenting : Suche auch in allen UIViewController ah unter dem UIViewController (wenn es natürlich gibt)
  • presented : UIViewController nach dem bereitgestellten (für StackIteratingFinder diese Option nicht sinnvoll, da sie immer von oben beginnt).

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


Ich würde empfehlen, sich in einem früheren Artikel mit dem Konzept der Container vertraut zu machen.


Beispiel Wenn Ihr Finder im gesamten Stapel nach einem AccountViewController suchen soll, jedoch nur unter den sichtbaren UIViewController , sollte dies folgendermaßen geschrieben werden:


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

NB Wenn aus irgendeinem Grund nur wenige Einstellungen zur Verfügung stehen, können Sie Ihre Implementierung von Finder a jederzeit problemlos schreiben. Ein Beispiel wird in diesem Artikel sein.


Kommen wir tatsächlich zu den Beispielen.


Beispiele für Konfigurationen mit Erläuterungen


Ich habe einen bestimmten UIViewController , den rootViewController UIWindow , und möchte, dass er am Ende der Navigation durch einen bestimmten HomeViewController ersetzt wird:


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

XibFactory lädt HomeViewController aus der xib-Datei von HomeViewController.xib


Vergessen Sie nicht, dass Sie, wenn Sie abstrakte Implementierungen von Finder und Factory in Kombination verwenden, den Typ von UIViewController und den Kontext für mindestens eine der Entitäten angeben müssen - ClassFinder<HomeViewController, Any?>


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


Die Konfiguration funktioniert so lange, bis sie aufgerufen wird, wenn ein modaler UIViewController auf dem Bildschirm angezeigt wird. In diesem Fall kann GeneralAction.replaceRoot den Root-Controller nicht ersetzen, da sich darüber ein modaler Controller befindet und der Router einen Fehler meldet. Wenn diese Konfiguration trotzdem funktionieren soll, müssen Sie dem Router erklären, dass GeneralAction.replaceRoot speziell auf den Root- UIViewController angewendet werden UIViewController . Dann entfernt der Router alle modal dargestellten UIViewController und die Konfiguration funktioniert in jeder Situation.


Ich möchte einen AccountViewController in einem UINavigationController AccountViewController , wenn er noch gut UINavigationController und der sich derzeit irgendwo auf dem Bildschirm befindet (selbst wenn sich dieser UINavigationController unter einem modalen UIViewController ):


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

Was bedeutet NilFactory in dieser Konfiguration? Auf diese Weise teilen Sie dem Router mit, dass er, wenn er keinen UINavigationController auf dem Bildschirm finden konnte, ihn nicht erstellen und in diesem Fall nur nichts tun soll. Da dies NilFactory , können Sie danach keine Action mehr verwenden.


Ich möchte einen AccountViewController in einem UINavigationController der sich noch irgendwo auf dem Bildschirm befindet. Wenn er sich nicht als solcher herausstellt, erstellen Sie ihn und zeigen Sie ihn 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 UITabBarController mit UITabBarController HomeViewController die HomeViewController und AccountViewController und diese durch das aktuelle Stammverzeichnis 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 das benutzerdefinierte UIViewControllerTransitioningDelegate mit der Aktion GeneralAction.presentModally :


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

Ich möchte zu AccountViewController gehen, wo immer sich der Benutzer befindet, auf einer anderen Registerkarte oder sogar in einem modalen Fenster:


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

Warum verwenden wir NilFactory ? Wir müssen keinen AccountViewController wenn er nicht gefunden wird. Es wird in der tabScreen Konfiguration erstellt. Sehen Sie sie oben.


Ich möchte ForgotPasswordViewController modal ForgotPasswordViewController , aber LoginViewController nach LoginViewController in 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() 

Sie können die Konfiguration im Beispiel für die Navigation in ForgotPasswordViewController und LoginViewController


Warum im obigen Beispiel Container expectingContainer ?


Da für die pushToNavigation Aktion ein UINavigationController und in der darauf folgenden Konfiguration erforderlich ist, können wir mit der UINavigationController Methode einen Kompilierungsfehler vermeiden, indem wir sicherstellen, dass der loginScreen , wenn der Router loginScreen UINavigationController .


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


Es wird funktionieren, aber da Sie dem Router mitteilen, dass er eine Kette aus dem Root- UIViewController , werden modale UIViewController über dem Router UIViewController , bevor Sie mit dem UIViewController der Kette beginnen.


Meine Anwendung verfügt über einen UITabBarController , der HomeViewController und BagViewController als Registerkarten enthält. Ich möchte, dass der Benutzer wie gewohnt mithilfe der Symbole auf den Registerkarten zwischen ihnen wechseln kann. Wenn ich die Konfiguration jedoch programmgesteuert HomeViewController (z. B. wenn der Benutzer im HomeViewController "Gehe zu Tasche" HomeViewController ), sollte die Anwendung die Registerkarte nicht wechseln, sondern den BagViewController modal BagViewController .


In der Konfiguration gibt es drei Möglichkeiten, dies zu erreichen:


  1. StackIteratingFinder Sie StackIteratingFinder , dass nur in sichtbaren mit [.current, .visible] gesucht wird.
  2. Verwenden Sie NilFinder was bedeutet, dass der Router den BagViewController niemals in den BagViewController und ihn immer erstellt. Dieser Ansatz hat jedoch einen Nebeneffekt: Wenn beispielsweise ein Benutzer, der sich bereits in BagViewController befindet, modal dargestellt wird und beispielsweise auf einen universellen Link BagViewController , den BagViewController soll, findet der Router ihn nicht und erstellt eine andere Instanz und zeigt ihn darüber an modal. Dies ist möglicherweise nicht das, was Sie wollen.
  3. Ändern Sie einen kleinen ClassFinder so, dass nur der BagViewController , der modal BagViewController , den Rest ignoriert und ihn bereits in der Konfiguration verwendet.

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

Anstelle einer Schlussfolgerung


Ich hoffe, die Methoden zur Konfiguration des Routers sind etwas klarer geworden. Wie gesagt, wir verwenden diesen Ansatz in drei Anwendungen und sind noch nicht auf eine Situation gestoßen, in der er nicht flexibel genug ist. Die Bibliothek sowie die Implementierung des ihr zur Verfügung gestellten Routers verwenden keine objektiven Tricks zur Laufzeit und folgen vollständig allen Cocoa Touch-Konzepten. Sie helfen nur dabei, den Kompositionsprozess in Schritte zu unterteilen und in der angegebenen Reihenfolge auszuführen und mit den iOS-Versionen 9 bis 12 zu testen Dieser Ansatz passt in alle Architekturmuster, bei denen mit dem UIViewController Stack gearbeitet wird (MVC, MVVM, VIP, RIB, VIPER usw.).


Ich würde mich über Ihre Kommentare und Vorschläge freuen. Vor allem, wenn Sie der Meinung sind, dass es sich lohnt, einige Aspekte genauer zu betrachten. Vielleicht muss das Konzept der Kontexte geklärt werden.

Source: https://habr.com/ru/post/de428990/


All Articles