
Die meisten mobilen Anwendungen enthalten mehr als ein Dutzend Bildschirme, komplexe Übergänge sowie Teile der Anwendung, die nach Bedeutung und Zweck getrennt sind. Daher muss die richtige Navigationsstruktur für die Anwendung organisiert werden, die flexibel, bequem und erweiterbar ist, einen bequemen Zugriff auf verschiedene Teile der Anwendung bietet und auch die Systemressourcen berücksichtigt.
In diesem Artikel werden wir die Navigation in der Anwendung so gestalten, dass die häufigsten Fehler vermieden werden, die zu Speicherverlusten führen, die Architektur ruinieren und die Navigationsstruktur beschädigen.
Die meisten Anwendungen bestehen aus mindestens zwei Teilen: Authentifizierung (Pre-Login) und privatem Teil (Post-Login). Einige Anwendungen haben möglicherweise eine komplexere Struktur, mehrere Profile mit einem einzigen Login, bedingte Übergänge nach dem Starten der Anwendung (Deeplinks) usw.
In der Praxis werden hauptsächlich zwei Ansätze zum Navigieren in der Anwendung verwendet:
- Ein Navigationsstapel für Präsentationscontroller (vorhanden) und Navigationscontroller (Push), ohne dass Sie zurückkehren können. Dieser Ansatz bewirkt, dass alle vorherigen ViewController im Speicher verbleiben.
- Verwendet den Schalter window.rootViewController. Bei diesem Ansatz werden alle vorherigen ViewController im Speicher zerstört, dies sieht jedoch aus Sicht der Benutzeroberfläche nicht optimal aus. Außerdem können Sie sich bei Bedarf nicht hin und her bewegen.
Lassen Sie uns nun sehen, wie Sie eine leicht zu wartende Struktur erstellen können, mit der Sie nahtlos zwischen verschiedenen Teilen der Anwendung wechseln können, ohne Spaghetti-Code und einfache Navigation.
Stellen wir uns vor, wir schreiben eine Anwendung bestehend aus:
- Primärbildschirm ( Begrüßungsbildschirm ): Dies ist der allererste Bildschirm, den Sie sehen. Sobald die Anwendung gestartet wird, können Sie beispielsweise Animationen hinzufügen oder primäre API-Anforderungen stellen.
- Authentifizierungsteil : Anmeldung, Registrierung, Zurücksetzen des Passworts, E-Mail-Bestätigung usw. Die Arbeitssitzung des Benutzers wird normalerweise gespeichert, sodass bei jedem Start der Anwendung kein Login eingegeben werden muss.
- Hauptteil : die Geschäftslogik der Hauptanwendung
Alle diese Teile der Anwendung sind voneinander isoliert und befinden sich in einem eigenen Navigationsstapel. Daher benötigen wir möglicherweise die folgenden Übergänge:
- Begrüßungsbildschirm -> Authentifizierungsbildschirm , wenn die aktuelle Sitzung des aktiven Benutzers fehlt.
- Begrüßungsbildschirm -> Hauptbildschirm, wenn sich der Benutzer bereits früher bei der Anwendung angemeldet hat und eine Sitzung aktiv ist.
- Hauptbildschirm -> Authentifizierungsbildschirm , falls sich der Benutzer abmeldet
GrundeinstellungWenn die Anwendung
gestartet wird , müssen wir den
RootViewController initialisieren, der zuerst geladen wird. Dies kann sowohl mit Code als auch über Interface Builder erfolgen. Erstellen Sie ein neues Projekt in xCode, und all dies wird standardmäßig bereits ausgeführt:
main.storyboard ist bereits an
window.rootViewController gebunden.
Um uns jedoch auf das Hauptthema des Artikels zu konzentrieren, werden wir in unserem Projekt keine Storyboards verwenden. Löschen
Sie daher
main.storyboard und
deaktivieren Sie das Feld "Hauptschnittstelle" unter Ziele -> Allgemein -> Bereitstellungsinformationen:

Ändern
wir nun die
didFinishLaunchingWithOptions- Methode in
AppDelegate so, dass sie folgendermaßen aussieht:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = RootViewController() window?.makeKeyAndVisible() return true }
Jetzt startet die Anwendung zuerst den
RootViewController . Benennen Sie den Basis-
ViewController in
RootViewController um :
class RootViewController: UIViewController { }
Dies ist der Hauptcontroller, der für alle Übergänge zwischen verschiedenen Abschnitten der Anwendung verantwortlich ist. Daher benötigen wir jedes Mal, wenn wir den Übergang vornehmen möchten, einen Link dazu.
Fügen Sie dazu die Erweiterung zu
AppDelegate hinzu :
extension AppDelegate { static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate } var rootViewController: RootViewController { return window!.rootViewController as! RootViewController } }
Das erzwungene Abrufen einer Option ist in diesem Fall gerechtfertigt, da sich der RootViewController nicht ändert. Wenn dies versehentlich geschieht, ist der Absturz der Anwendung eine normale Situation.
Jetzt haben wir also von überall in der Anwendung einen Link zum
RootViewController :
let rootViewController = AppDelegate.shared.rootViewController
Jetzt erstellen wir einige weitere Controller, die wir benötigen:
SplashViewController, LoginViewController und
MainViewController .
Der Begrüßungsbildschirm ist der erste Bildschirm, den ein Benutzer nach dem Starten der Anwendung sieht. Zu diesem Zeitpunkt werden normalerweise alle erforderlichen API-Anforderungen gestellt, die Aktivität der Benutzersitzung wird überprüft usw. Verwenden Sie die
UIActivityIndicatorView, um die laufenden Hintergrundaktionen
anzuzeigen :
class SplashViewController: UIViewController { private let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.white view.addSubview(activityIndicator) activityIndicator.frame = view.bounds activityIndicator.backgroundColor = UIColor(white: 0, alpha: 0.4) makeServiceCall() } private func makeServiceCall() { } }
Um API-Anforderungen zu simulieren, fügen Sie die Methode
DispatchQueue.main.asyncAfter mit einer Verzögerung von 3 Sekunden hinzu:
private func makeServiceCall() { activityIndicator.startAnimating() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(3)) { self.activityIndicator.stopAnimating() } }
Wir glauben, dass die Benutzersitzung auch in diesen Anforderungen festgelegt ist. In unserer Anwendung verwenden wir
UserDefaults dafür:
private func makeServiceCall() { activityIndicator.startAnimating() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(3)) { self.activityIndicator.stopAnimating() if UserDefaults.standard.bool(forKey: “LOGGED_IN”) {
Sie werden UserDefaults definitiv nicht verwenden, um den Status einer Benutzersitzung in der Release-Version des Programms zu speichern. Wir verwenden lokale Einstellungen in unserem Projekt, um das Verständnis zu vereinfachen und nicht viel über das Hauptthema des Artikels hinauszugehen.Erstellen Sie einen
LoginViewController . Es wird verwendet, um den Benutzer zu authentifizieren, wenn die aktuelle Benutzersitzung inaktiv ist. Sie können Ihre benutzerdefinierte Benutzeroberfläche zum Controller hinzufügen, aber ich werde hier nur den Bildschirmtitel und die Anmeldeschaltfläche in der Navigationsleiste hinzufügen.
class LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.white title = "Login Screen" let loginButton = UIBarButtonItem(title: "Log In", style: .plain, target: self, action: #selector(login)) navigationItem.setLeftBarButton(loginButton, animated: true) } @objc private func login() {
Erstellen Sie abschließend den
Hauptcontroller der
MainViewController- Anwendung:
class MainViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.lightGray // to visually distinguish the protected part title = “Main Screen” let logoutButton = UIBarButtonItem(title: “Log Out”, style: .plain, target: self, action: #selector(logout)) navigationItem.setLeftBarButton(logoutButton, animated: true) } @objc private func logout() { // clear the user session (example only, not for the production) UserDefaults.standard.set(false, forKey: “LOGGED_IN”) // navigate to the Main Screen } }
Root-NavigationNun zurück zum
RootViewController .
Wie bereits erwähnt, ist
RootViewController das einzige Objekt, das für Übergänge zwischen verschiedenen unabhängigen Controller-Stacks verantwortlich ist. Um den aktuellen Status der Anwendung zu kennen, erstellen wir eine Variable, in der wir den aktuellen
ViewController speichern:
class RootViewController: UIViewController { private var current: UIViewController }
Fügen Sie den Klasseninitialisierer hinzu und erstellen Sie den ersten
ViewController , den wir beim Start der Anwendung laden möchten. In unserem Fall ist es
SplashViewController :
class RootViewController: UIViewController { private var current: UIViewController init() { self.current = SplashViewController() super.init(nibName: nil, bundle: nil) } }
Fügen Sie in
viewDidLoad den aktuellen
viewController zum
RootViewController hinzu :
class RootViewController: UIViewController { ... override func viewDidLoad() { super.viewDidLoad() addChildViewController(current)
Sobald wir
childViewController (1)
hinzugefügt haben , passen wir seine Größe an, indem wir
current.view.frame auf
view.bounds (2) setzen.
Wenn wir diese Zeile überspringen, wird der
viewController in den meisten Fällen immer noch korrekt platziert. Bei Änderungen der
Frame- Größe können jedoch Probleme auftreten.
Fügen Sie eine neue Unteransicht (3) hinzu und rufen Sie die Methode didMove (toParentViewController :) auf. Damit ist der Vorgang zum Hinzufügen des Controllers abgeschlossen. Sobald der
RootViewController hochfährt , wird unmittelbar danach der
SplashViewController angezeigt.
Jetzt können Sie der Anwendung verschiedene Navigationsmethoden hinzufügen. Wir werden den
LoginViewController ohne Animation anzeigen, der
MainViewController wird die Animation mit
sanftem Dimmen verwenden und der Übergang der Bildschirme beim Trennen des Benutzers hat einen Folieneffekt.
class RootViewController: UIViewController { ... func showLoginScreen() { let new = UINavigationController(rootViewController: LoginViewController()) // 1 addChildViewController(new) // 2 new.view.frame = view.bounds // 3 view.addSubview(new.view) // 4 new.didMove(toParentViewController: self) // 5 current.willMove(toParentViewController: nil) // 6 current.view.removeFromSuperview()] // 7 current.removeFromParentViewController() // 8 current = new // 9 }
Erstellen Sie den
LoginViewController (1), fügen Sie ihn als
untergeordneten Controller hinzu (2) und setzen Sie den Frame (3). Fügen Sie die Ansicht von
LoginController als Unteransicht (4) hinzu und rufen Sie die Methode didMove (5) auf. Bereiten Sie als Nächstes den aktuellen Controller für das Entfernen mit der Methode willMove (6) vor. Löschen Sie abschließend die aktuelle Ansicht aus der Übersicht (7) und den aktuellen Controller aus
RootViewController (8). Denken Sie daran, den Wert des aktuellen Reglers (9) zu aktualisieren.
Erstellen
wir nun die
switchToMainScreen- Methode:
func switchToMainScreen() { let mainViewController = MainViewController() let mainScreen = UINavigationController(rootViewController: mainViewController) ... }
Das Animieren des Übergangs erfordert eine andere Methode:
private func animateFadeTransition(to new: UIViewController, completion: (() -> Void)? = nil) { current.willMove(toParentViewController: nil) addChildViewController(new) transition(from: current, to: new, duration: 0.3, options: [.transitionCrossDissolve, .curveEaseOut], animations: { }) { completed in self.current.removeFromParentViewController() new.didMove(toParentViewController: self) self.current = new completion?()
Diese Methode ist
showLoginScreen sehr ähnlich, aber alle letzten Schritte werden ausgeführt, nachdem die Animation abgeschlossen ist. Um die aufrufende Methode über das Ende des Übergangs zu informieren, rufen wir ganz am Ende den Abschluss (1) auf.
Die endgültige Version der
switchToMainScreen- Methode sieht nun folgendermaßen aus:
func switchToMainScreen() { let mainViewController = MainViewController() let mainScreen = UINavigationController(rootViewController: mainViewController) animateFadeTransition(to: mainScreen) }
Und schließlich erstellen wir die letzte Methode, die für den Übergang von
MainViewController zu
LoginViewController verantwortlich ist :
func switchToLogout() { let loginViewController = LoginViewController() let logoutScreen = UINavigationController(rootViewController: loginViewController) animateDismissTransition(to: logoutScreen) }
Die
AnimateDismissTransition- Methode bietet
Folienanimation :
private func animateDismissTransition(to new: UIViewController, completion: (() -> Void)? = nil) { new.view.frame = CGRect(x: -view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height) current.willMove(toParentViewController: nil) addChildViewController(new) transition(from: current, to: new, duration: 0.3, options: [], animations: { new.view.frame = self.view.bounds }) { completed in self.current.removeFromParentViewController() new.didMove(toParentViewController: self) self.current = new completion?() } }
Dies sind nur zwei Beispiele für Animationen. Mit demselben Ansatz können Sie alle komplexen Animationen erstellen, die Sie benötigen
Fügen Sie zum Abschließen der Konfiguration Methodenaufrufe mit Animationen von
SplashViewController, LoginViewController und
MainViewController hinzu :
class SplashViewController: UIViewController { ... private func makeServiceCall() { if UserDefaults.standard.bool(forKey: “LOGGED_IN”) { // navigate to protected page AppDelegate.shared.rootViewController.switchToMainScreen() } else { // navigate to login screen AppDelegate.shared.rootViewController.switchToLogout() } } } class LoginViewController: UIViewController { ... @objc private func login() { ... AppDelegate.shared.rootViewController.switchToMainScreen() } } class MainViewController: UIViewController { ... @objc private func logout() { ... AppDelegate.shared.rootViewController.switchToLogout() } }
Kompilieren Sie die Anwendung, führen Sie sie aus und überprüfen Sie ihren Betrieb auf zwei Arten:
- wenn der Benutzer bereits eine aktive aktuelle Sitzung hat (angemeldet)
- wenn keine aktive Sitzung vorhanden ist und eine Authentifizierung erforderlich ist
In beiden Fällen sollte unmittelbar nach dem Laden von
SplashScreen ein Übergang zum gewünschten Bildschirm angezeigt werden.

Als Ergebnis haben wir ein kleines Testmodell der Anwendung erstellt, mit Navigation durch die Hauptmodule. Wenn Sie die Funktionen der Anwendung erweitern, zusätzliche Module und Übergänge zwischen ihnen hinzufügen müssen, können Sie dieses Navigationssystem jederzeit schnell und bequem erweitern und skalieren.