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:
ProductViewController
findet ProductViewController
und der Router ProductViewController
fortNilFinder
wird niemals etwas finden und der Router wird NilFinder
GeneralStep.current
gibt immer den obersten UIViewController
auf dem Stapel zurück.UIViewController
, wird der Router zurückgeschaltetUINavigationController
einen UINavigationController
mit der NavigationControllerFactory- Zeigt es modal mit
GeneralAction.presentModally
ProductViewController
ProductViewController ProductViewControllerFactory
- Integriert den erstellten
UINavigationController
mithilfe von UINavigationController.pushToNavigation
in den vorherigen UINavigationController.pushToNavigation
- 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))
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()
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:
StackIteratingFinder
Sie StackIteratingFinder
, dass nur in sichtbaren mit [.current, .visible] gesucht wird.- 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. - Ä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.