Atomic Design und System Design sind im Design beliebt: Hier besteht alles aus Komponenten, von Steuerelementen bis zu Bildschirmen. Für einen Programmierer ist es nicht schwierig, separate Steuerelemente zu schreiben, aber was tun mit ganzen Bildschirmen?
Lassen Sie uns das Neujahrsbeispiel analysieren:
- lasst uns alles zusammenhalten;
- unterteilt in Controller: Navigation, Vorlage und Inhalt auswählen;
- Code für andere Bildschirme wiederverwenden.

Alles in einem Haufen
Auf dem Neujahrsbildschirm werden die besonderen Öffnungszeiten von Pizzerien beschrieben. Es ist ganz einfach, daher wird es kein Verbrechen sein, es zu einem einzigen Controller zu machen:

Aber. Wenn wir das nächste Mal einen ähnlichen Bildschirm benötigen, müssen wir ihn noch einmal wiederholen und dann an allen Bildschirmen die gleichen Änderungen vornehmen. Nun, es geht nicht ohne Änderungen.
Daher ist es sinnvoller, es in Teile zu unterteilen und für andere Bildschirme zu verwenden. Ich habe drei hervorgehoben:
- Navigation
- eine Vorlage mit einem Bereich für Inhalte und einem Platz für Aktionen am unteren Bildschirmrand,
- einzigartiger Inhalt in der Mitte.
Wählen Sie jedes Teil in einem eigenen UIViewController
.
Containernavigation
Die auffälligsten Beispiele für Navigationscontainer sind der UINavigationController
und der UITabBarController
. Jeder belegt unter seinen eigenen Steuerelementen einen Streifen auf dem Bildschirm und lässt den verbleibenden Platz für einen anderen UIViewController
.
In unserem Fall gibt es einen Container für alle modalen Bildschirme mit nur einer Schaltfläche zum Schließen.
Was ist der Sinn?Wenn wir die Schaltfläche nach rechts verschieben möchten, müssen wir sie nur in einem Controller ändern.
Oder wenn wir alle modalen Fenster mit einer speziellen Animation anzeigen und interaktiv mit einem Wisch schließen möchten, wie in den AppStore-Storykarten. Dann muss UIViewControllerTransitioningDelegate
nur für diesen Controller festgelegt werden.

Sie können eine container view
, um Controller zu trennen: Sie erstellt eine UIView
im übergeordneten Controller und fügt die UIView
Controllers darin ein.

Strecken Sie die container view
bis zum Bildschirmrand. Safe area
gilt automatisch für den untergeordneten Controller:

Bildschirmmuster
Der Inhalt ist auf dem Bildschirm offensichtlich: Bild, Titel, Text. Die Schaltfläche scheint Teil davon zu sein, aber der Inhalt ist auf verschiedenen iPhones dynamisch und die Schaltfläche ist fest. Es sind zwei Systeme mit unterschiedlichen Aufgaben sichtbar: eines zeigt Inhalte an und das andere bettet sie ein und richtet sie aus. Sie sollten in zwei Controller unterteilt werden.

Der erste ist für das Layout des Bildschirms verantwortlich: Der Inhalt sollte zentriert und die Schaltfläche am unteren Bildschirmrand festgenagelt sein. Der zweite wird den Inhalt zeichnen.

Ohne Vorlage sind alle Controller ähnlich, aber die Elemente tanzen.
Die Schaltflächen auf dem letzten Bildschirm sind unterschiedlich - dies hängt vom Inhalt ab. Die Delegierung hilft bei der Lösung des Problems: Die Controller-Vorlage UIStackView
Steuerelemente aus dem Inhalt an und zeigt sie in ihrer UIStackView
.
Schaltflächen können über verwandte Objekte an die Steuerung angehängt werden. Ihr IBOutlet
und ihre IBAction
werden im IBAction
gespeichert, nur die Elemente werden nicht zur Hierarchie hinzugefügt.

Sie können Elemente aus dem Inhalt UIStoryboardSegue
und in der Vorbereitungsphase von UIStoryboardSegue
zur Vorlage UIStoryboardSegue
:
Im Setter fügen wir UIStackView
Steuerelemente UIStackView
:
Infolgedessen wurde unser Controller in drei Teile unterteilt: Navigation, Vorlage und Inhalt. In der Abbildung werden alle container view
grau dargestellt:

Größe des dynamischen Controllers
Der Content Controller hat seine eigene maximale Größe und ist durch interne constraints
.
Container view
auf der Autoresizing mask
der Autoresizing mask
, die mit den internen Dimensionen des Inhalts in Konflikt stehen. Das Problem wird im Code gelöst: Im Inhaltscontroller müssen Sie angeben, dass es nicht von den Konstoren der Autoresizing mask
betroffen ist:

Es gibt zwei weitere Schritte für Interface Builder:
Schritt 1. UIView
die UIView
für die UIView
. Nach dem Start werden echte Werte angezeigt, aber wir werden vorerst alle geeigneten Werte angeben.

Schritt 2. Geben Sie für den Inhaltscontroller die Simulated Size
. Es entspricht möglicherweise nicht der vorherigen Größe.
Es gab Layoutfehler. Was soll ich tun?Fehler treten auf, wenn AutoLayout
nicht AutoLayout
kann, wie die Elemente in der aktuellen Größe zerlegt werden.
Meistens verschwindet das Problem, nachdem die Prioritäten der Konstante geändert wurden. Sie müssen sie ablegen, damit einer der UIView
mehr als die anderen erweitern / UIView
kann.
Wir teilen uns in Teile und schreiben Code
Wir haben den Controller in mehrere Teile unterteilt, aber bisher können wir sie nicht wiederverwenden. Die Schnittstelle von UIStoryboard
in Teilen schwer zu extrahieren. Wenn wir einige Daten auf den Inhalt übertragen müssen, müssen wir sie durch die gesamte Hierarchie ziehen. Es sollte umgekehrt sein: Nehmen Sie zuerst den Inhalt, konfigurieren Sie ihn und verpacken Sie ihn dann in die erforderlichen Container. Wie eine Glühbirne.
Drei Aufgaben erscheinen auf unserem Weg:
- Trennen Sie jeden Controller in ein eigenes
UIStoryboard
. container view
ablehnen, Controller zu Containern im Code hinzufügen.- Binde alles zurück.
UIStoryboard teilen
Sie müssen zwei zusätzliche UIStoryboard
erstellen und den Navigationscontroller und den Vorlagencontroller kopieren und in diese einfügen. Embed segue
wird unterbrochen, aber die container view
mit den konfigurierten Einschränkungen wird übertragen. Einschränkungen müssen gespeichert und die container view
durch eine reguläre UIView
.
Am einfachsten ist es, den Typ der Containeransicht im UIStoryboard-Code zu ändern.UIStoryboard
als Code öffnen (Dateikontextmenü → Öffnen als ... → Quellcode);Ändern Sie den Typ von containerView
in view
. Es ist notwendig, sowohl das öffnende als auch das schließende Tag zu ändern.
Auf die gleiche Weise können Sie beispielsweise UIView
bei UIScrollView
in UIScrollView
ändern. Umgekehrt.

Wir setzen den Controller auf die Eigenschaft " is initial view controller
und rufen das UIStoryboard
als Controller auf.
Wir laden den Controller von UIStoryboard.Wenn der Name des Controllers mit dem Namen von UIStoryboard
, kann der Download in eine Methode eingeschlossen werden, die selbst die gewünschte Datei findet:
protocol Storyboardable { } extension Storyboardable where Self: UIViewController { static func instantiateInitialFromStoryboard() -> Self { let controller = storyboard().instantiateInitialViewController() return controller! as! Self } static func storyboard(fileName: String? = nil) -> UIStoryboard { let storyboard = UIStoryboard(name: fileName ?? storyboardIdentifier, bundle: nil) return storyboard } static var storyboardIdentifier: String { return String(describing: self) } static var storyboardName: String { return storyboardIdentifier } }
Wenn der Controller in .xib
, wird der Standardkonstruktor ohne solche Tänze geladen. .xib
kann .xib
nur einen Controller enthalten, oft reicht dies nicht aus: In einem guten Fall besteht ein Bildschirm aus mehreren. Daher verwenden wir UIStoryborad
, es ist einfach, den Bildschirm in Teile zu UIStoryborad
.
Fügen Sie einen Controller im Code hinzu
Damit der Controller ordnungsgemäß funktioniert, benötigen wir alle Methoden seines Lebenszyklus: will/did-appear/disappear
.
Für die korrekte Anzeige müssen Sie 5 Schritte aufrufen:
willMove(toParent parent: UIViewController?) addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
Apple schlägt vor, den Code auf 4 Schritte zu reduzieren, da addChild()
selbst willMove(toParent)
. Zusammenfassend:
addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
Der Einfachheit halber können Sie alles in extension
einwickeln. Für unseren Fall benötigen wir eine Version mit insertSubview()
.
extension UIViewController { func insertFullframeChildController(_ childController: UIViewController, toView: UIView? = nil, index: Int) { let containerView: UIView = toView ?? view addChild(childController) containerView.insertSubview(childController.view, at: index) containerView.pinToBounds(childController.view) childController.didMove(toParent: self) } }
Zum Löschen benötigen Sie dieselben Schritte, nur müssen Sie anstelle des übergeordneten Controllers nil
festlegen. Jetzt ruft removeFromParent()
didMove(toParent: nil)
, und das Layout wird nicht benötigt. Die verkürzte Version ist sehr unterschiedlich:
willMove(toParent: nil) view.removeFromSuperview() removeFromParent()
Layout
Einschränkungen festlegen
Um die Größe des Controllers richtig einzustellen, verwenden wir AutoLayout
. Wir müssen alle Seiten an alle Seiten nageln:
extension UIView { func pinToBounds(_ view: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: topAnchor), view.bottomAnchor.constraint(equalTo: bottomAnchor), view.leadingAnchor.constraint(equalTo: leadingAnchor), view.trailingAnchor.constraint(equalTo: trailingAnchor) ]) } }
Fügen Sie einen untergeordneten Controller in den Code ein
Jetzt kann alles kombiniert werden:
Aufgrund der Häufigkeit der Nutzung können wir all dies in extension
:
Eine ähnliche Methode wird auch für den Vorlagencontroller benötigt. prepare(for segue:)
wurden prepare(for segue:)
in prepare(for segue:)
, jetzt können Sie sie in die Controller-Einbettungsmethode einbinden:
Das Erstellen eines Controllers sieht folgendermaßen aus:
Das Anschließen eines neuen Bildschirms an die Vorlage ist einfach:
- entfernen, was für den Inhalt nicht relevant ist;
- Geben Sie Aktionsschaltflächen an, indem Sie das OnboardingViewControllerDatasource-Protokoll implementieren.
- Schreiben Sie eine Methode, die eine Vorlage und einen Inhalt verknüpft.
Mehr über Container
Statusleiste
Die Sichtbarkeit der status bar
muss häufig von einem Controller mit Inhalt und nicht von einem Container gesteuert werden. Dafür gibt es ein paar property
:
Mit diesen property
Sie eine Kette von Controllern erstellen, die für die Anzeige der status bar
.
Sicherer Bereich
Wenn sich die Container-Schaltflächen mit dem Inhalt überschneiden, sollten Sie die safeArea
Zone erhöhen. Dies kann im folgenden Code erfolgen: set additinalSafeAreaInsets
für additinalSafeAreaInsets
Controller. Sie können es von embedController()
aus embedController()
:
private func addSafeArea(to controller: UIViewController) { if #available(iOS 11.0, *) { let buttonHeight = CGFloat(30) let topInset = UIEdgeInsets(top: buttonHeight, left: 0, bottom: 0, right: 0) controller.additionalSafeAreaInsets = topInset } }
Wenn Sie oben 30 Punkte hinzufügen, hört die Schaltfläche auf, Inhalte zu überlappen, und safeArea
den grünen Bereich:

Ränder. Beibehalten der Überwachungsränder
Controller haben Standardmargen. Normalerweise entsprechen sie 16 Punkten von jeder Seite des Bildschirms und nur bei Übergrößen sind es 20 Punkte.
Basierend auf den margins
Sie Konstanten erstellen. Die Einrückung am Rand ist für verschiedene iPhones unterschiedlich:

Wenn wir eine UIView
in eine andere UIView
, halbieren sich die margins
: auf 8 Punkte. Um dies zu verhindern, müssen Sie die Preserve superview margins
. Dann sind die margins
UIView
gleich den margins
übergeordneten UIView
. Es ist für Vollbildbehälter geeignet.
Das Ende
Container-Controller sind ein leistungsstarkes Werkzeug. Sie vereinfachen den Code, trennen Aufgaben und können wiederverwendet werden. Sie können verschachtelte Controller auf beliebige Weise schreiben: in UIStoryboard
, in .xib
oder einfach in Code. Vor allem sind sie einfach zu erstellen und machen Spaß.
→ Ein Beispiel aus einem Artikel auf GitHub
Haben Sie Bildschirme, auf denen es sich lohnt, eine Vorlage zu erstellen? Teilen Sie in den Kommentaren!