Hallo Habr! Ich präsentiere Ihnen die Übersetzung des Artikels 
Programmgesteuertes Erstellen von UI-Elementen mit PureLayout von Aly Yaka.

Willkommen zum zweiten Teil des Artikels über das programmgesteuerte Erstellen einer Schnittstelle mit PureLayout. 
Im ersten Teil haben wir die Benutzeroberfläche einer einfachen mobilen Anwendung vollständig im Code erstellt, ohne Storyboards oder NIBs zu verwenden. In diesem Handbuch werden einige der am häufigsten verwendeten Elemente der Benutzeroberfläche in allen Anwendungen behandelt:
- UINavigationController / Bar
- UITableView
- UITableViewCell in Selbstgröße
UINavigationController
In unserer Anwendung benötigen Sie wahrscheinlich eine Navigationsleiste, damit der Benutzer von der Kontaktliste zu detaillierten Informationen über einen bestimmten Kontakt wechseln und dann zur Liste zurückkehren kann. 
UINavigationController kann dieses Problem mithilfe der Navigationsleiste problemlos lösen.
UINavigationController ist nur ein Stapel, in dem Sie viele Ansichten verschieben. Der Benutzer sieht gerade die oberste Ansicht (die zuletzt verschobene) (es sei denn, Sie haben darüber eine andere Ansicht angezeigt, sagen wir Favoriten). Wenn Sie die Draufsicht-Controller des Navigations-Controllers drücken, erstellt der Navigations-Controller automatisch eine Schaltfläche „Zurück“ (je nach den aktuellen Spracheinstellungen des Geräts oben links oder rechts). Wenn Sie auf diese Schaltfläche klicken, kehren Sie zur vorherigen Ansicht zurück.
All dies wird vom Navigationscontroller sofort erledigt. Wenn Sie eine weitere hinzufügen, wird nur eine zusätzliche Codezeile benötigt (wenn Sie die Navigationsleiste nicht anpassen möchten).
Gehen Sie zu AppDelegate.swift und fügen Sie die folgende Codezeile hinzu. Lassen Sie 
viewController = ViewController (): let navigationController = UINavigationController(rootViewController: viewController) 
Jetzt 
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController ändern 
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController self.window? .RootViewController = viewController self.window? .RootViewController = navigationController self.window? .RootViewController = viewController self.window? .RootViewController = navigationController . In der ersten Zeile haben wir eine Instanz des 
UINavigationController und ihm unseren 
viewController als 
rootViewController ist der Ansichtscontroller ganz unten im Stapel. Dies bedeutet, dass in der Navigationsleiste dieser Ansicht niemals eine Zurück-Schaltfläche 
rootViewController wird. Dann geben wir unserem Fenster einen Navigationscontroller als 
rootViewController , da dieser nun alle Ansichten in der Anwendung enthält.
Führen Sie nun Ihre Anwendung aus. Das Ergebnis sollte folgendermaßen aussehen:
Leider ist etwas schief gelaufen. Es scheint, dass die Navigationsleiste unsere obere Ansicht überlappt, und wir haben verschiedene Möglichkeiten, dies zu beheben:
- upperViewdie Größe unserer- upperViewAnsicht, um sie an die Höhe der Navigationsleiste- upperView.
- Setzen Sie die Eigenschaft isTranslucentder Navigationsleiste auffalse. Dadurch wird die Navigationsleiste undurchsichtig (wenn Sie nicht bemerkt haben, dass sie etwas transparent ist), und jetzt wird der obere Rand der Übersicht zum unteren Rand der Navigationsleiste.
Ich persönlich werde die zweite Option wählen, aber Sie werden die erste studieren. Ich empfehle außerdem, die Dokumente von Apple auf dem 
UINavigationController und der 
UINavigationBar überprüfen und sorgfältig zu lesen:
Gehen Sie nun zur viewDidLoad-Methode und fügen Sie diese Zeile hinzu 
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad () self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad () , so dass es so aussehen würde:
 override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.view.backgroundColor = .white self.addSubviews() self.setupConstraints() self.view.bringSubview(toFront: avatar) self.view.setNeedsUpdateConstraints() } 
Sie können diese Zeile auch 
self.title = "John Doe" viewDidLoad hinzufügen, wodurch der Navigationsleiste ein "Profil" 
self.title = "John Doe" viewDidLoad wird, damit der Benutzer weiß, wo er sich gerade befindet. Führen Sie die Anwendung aus, und das Ergebnis sollte folgendermaßen aussehen:
Refactoring unseres View Controllers
Bevor wir fortfahren, müssen wir unsere 
ViewController.swift Datei reduzieren, 
ViewController.swift wir nur echte Logik verwenden können, nicht nur Code für Benutzeroberflächenelemente. Wir können dies tun, indem wir eine Unterklasse von 
UIView und alle Elemente unserer Benutzeroberfläche dorthin verschieben. Der Grund dafür ist, dass wir kurz dem Model-View-Controller- oder MVC-Architekturmuster folgen. Erfahren Sie mehr über MVC 
Model-View-Controller (MVC) unter iOS: ein moderner Ansatz .
Klicken Sie nun mit der rechten Maustaste auf den 
ContactCard Ordner in Project Navigator und wählen Sie "Neue Datei":

Klicken Sie auf Cocoa Touch Class und dann auf Weiter. Schreiben Sie nun "ProfileView" als Klassennamen und geben Sie neben "Subclass of:" unbedingt "UIView" ein. Es weist Xcode lediglich an, unsere Klasse automatisch von 
UIView erben zu 
UIView , und es wird etwas Boilerplate-Code hinzugefügt. Klicken Sie nun auf Weiter und dann auf Auskommentierten Code und löschen Sie ihn:
 
Und jetzt sind wir bereit für das Refactoring.
Schneiden Sie alle faulen Variablen aus dem Ansichts-Controller aus und fügen Sie sie in unsere neue Ansicht ein.
Überschreiben Sie unter der letzten ausstehenden Variablen 
init(frame :) indem Sie 
init eingeben und dann das erste Ergebnis der automatischen Vervollständigung aus Xcode auswählen.

Es wird ein Fehler angezeigt, der besagt, dass der "erforderliche" Initialisierer "init (coder :)" von einer Unterklasse von "UIView" bereitgestellt werden sollte:

Sie können dies beheben, indem Sie auf den roten Kreis und dann auf Beheben klicken.

In jedem überschriebenen Initialisierer sollten Sie fast immer den Superklassen-Initialisierer aufrufen. 
super.init (frame: frame) daher diese Codezeile oben in der Methode hinzu: 
super.init (frame: frame) .
Schneiden Sie die 
addSubviews() -Methode aus und fügen 
addSubviews() unter den Initialisierern ein. Löschen Sie 
self.view vor jedem 
addSubview Aufruf.
 func addSubviews() { addSubview(avatar) addSubview(upperView) addSubview(segmentedControl) addSubview(editButton) } 
Rufen Sie dann diese Methode vom Initialisierer aus auf:
 override init(frame: CGRect) { super.init(frame: frame) addSubviews() bringSubview(toFront: avatar) } 
Überschreiben 
updateConstraints() für Einschränkungen 
updateConstraints() und fügen Sie am Ende dieser Funktion einen Aufruf hinzu (wo er immer verbleibt):
 override func updateConstraints() {  
Wenn Sie eine Methode überschreiben, ist es immer nützlich, die Dokumentation zu überprüfen, indem Sie Apple-Dokumente aufrufen oder einfacher die Wahltaste (oder die Alt-Taste) gedrückt halten und auf den Funktionsnamen klicken:

Schneiden Sie den Einschränkungscode aus dem Ansichts-Controller aus und fügen Sie ihn in unsere neue Methode ein:
 override func updateConstraints() { avatar.autoAlignAxis(toSuperviewAxis: .vertical) avatar.autoPinEdge(toSuperviewEdge: .top, withInset: 64.0) upperView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom) segmentedControl.autoPinEdge(toSuperviewEdge: .left, withInset: 8.0) segmentedControl.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) segmentedControl.autoPinEdge(.top, to: .bottom, of: avatar, withOffset: 16.0) editButton.autoPinEdge(.top, to: .bottom, of: upperView, withOffset: 16.0) editButton.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) super.updateConstraints() } 
ProfileView nun zum View Controller zurück und initialisieren Sie die 
ProfileView Instanz über die 
viewDidLoad Methode. 
let profileView = ProfileView(frame: .zero) und fügen Sie sie dem 
ViewController als Unteransicht 
ViewController .
Jetzt wurde unser View Controller auf wenige Codezeilen reduziert!
 import UIKit import PureLayout class ViewController: UIViewController { let profileView = ProfileView(frame: .zero) override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.title = "Profile" self.view.backgroundColor = .white self.view.addSubview(self.profileView) self.profileView.autoPinEdgesToSuperviewEdges() self.view.layoutIfNeeded() } } 
Um sicherzustellen, dass alles wie beabsichtigt funktioniert, starten Sie Ihre Anwendung und überprüfen Sie, wie sie aussieht.
Ein dünner, ordentlicher Überprüfungscontroller sollte immer Ihr Ziel sein. Dies kann viel Zeit in Anspruch nehmen, erspart Ihnen jedoch unnötige Probleme bei der Wartung.
UITableView
Als nächstes fügen wir eine UITableView hinzu, um Kontaktinformationen wie Telefonnummer, Adresse usw. anzuzeigen.
Wenn Sie dies noch nicht getan haben, lesen Sie UITableView, UITableViewDataSource und UITableViewDelegate in der Apple-Dokumentation.
Gehen Sie zu 
ViewController.swift und fügen Sie 
lazy var für 
tableView über 
viewDidLoad() :
 lazy var tableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self tableView.dataSource = self return tableView }() 
Wenn Sie versuchen, die Anwendung auszuführen, beschwert sich Xcode, dass diese Klasse weder ein Delegat noch eine Datenquelle für den 
UITableViewController ist. Daher fügen wir der Klasse diese beiden Protokolle hinzu:
 class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { . . . 
Erneut beschwert sich Xcode über eine Klasse, die nicht dem 
UITableViewDataSource Protokoll entspricht. Dies bedeutet, dass dieses Protokoll obligatorische Methoden enthält, die nicht in der Klasse definiert sind. Um herauszufinden, welche dieser Methoden Sie implementieren sollten, während Sie Cmd + Control 
UITableViewDataSource halten, klicken 
UITableViewDataSource in der Klassendefinition auf das 
UITableViewDataSource Protokoll, und Sie fahren mit der Protokolldefinition fort. Für jede Methode, der das Wort 
optional nicht vorangestellt ist, muss eine diesem Protokoll entsprechende Klasse implementiert werden.
Hier haben wir zwei Methoden, die wir implementieren müssen:
- public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int- Diese Methode teilt der Tabellenansicht mit, wie viele Zeilen- public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int.
- public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell- Diese Methode fragt die Zelle in jeder Zeile ab. Hier initialisieren wir die Zelle (oder verwenden sie wieder) und fügen die Informationen ein, die wir dem Benutzer anzeigen möchten. In der ersten Zelle wird beispielsweise die Telefonnummer angezeigt, in der zweiten Zelle die Adresse usw.
ViewController.swift nun zu 
ViewController.swift zurück, geben 
ViewController.swift numberOfRowsInSection und wählen Sie die erste Option aus, wenn die automatische Vervollständigung angezeigt wird.
 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { <#code#> } 
Löschen Sie den Wortcode und kehren Sie jetzt zurück 1.
 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } 
cellForRowAt unter dieser Funktion mit der Eingabe von 
cellForRowAt und wählen Sie die erste Methode aus der automatischen Vervollständigung erneut aus.
 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { <#code#> } 
Und geben Sie 
UITableViewCell wieder eine 
UITableViewCell .
 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() } 
ProfileView nun unsere Tabellenansicht in 
ProfileView zu verbinden, definieren wir einen neuen Initialisierer, der die Tabellenansicht als Parameter verwendet, damit er sie als Unteransicht hinzufügen und die entsprechenden Einschränkungen dafür festlegen kann.
Gehen Sie zu 
ProfileView.swift und fügen Sie das Attribut für die Tabellenansicht direkt über dem Initialisierer hinzu:
var tableView: UITableView! Daher sind wir nicht sicher, ob es konstant sein wird.
Ersetzen Sie nun die alte 
init (frame :) Implementierung durch:
 init(tableView: UITableView) { super.init(frame: .zero) self.tableView = tableView addSubviews() bringSubview(toFront: avatar) } 
Xcode wird sich jetzt über den fehlenden 
init (frame :) für 
ProfileView . Gehen Sie also zurück zu 
ViewController.swift und ersetzen Sie 
let profileView = ProfileView (frame: .zero) durch
 lazy var profileView: UIView = { return ProfileView(tableView: self.tableView) }() 
Jetzt hat unsere 
ProfileView einen Link zu einer Tabellenansicht, und wir können sie als Unteransicht hinzufügen und die richtigen Einschränkungen dafür festlegen.
Zurück zu 
ProfileView.swift , fügen Sie 
addSubview(tableView) am Ende von 
addSubviews() und setzen Sie diese Einschränkungen auf 
updateConstraints() über 
super.updateConstraints :
 tableView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top) tableView.autoPinEdge(.top, to: .bottom, of: segmentedControl, withOffset: 8) 
In der ersten Zeile werden drei Einschränkungen zwischen der Tabellenansicht und ihrer Übersicht hinzugefügt: Die rechte, linke und untere Seite der Tabellenansicht sind an die rechte, linke und untere Seite der Profilansicht angehängt.
In der zweiten Zeile wird der obere Rand der Tabellenansicht mit einem Abstand von acht Punkten am unteren Rand des segmentierten Steuerelements angehängt. Starten Sie die Anwendung und das Ergebnis sollte folgendermaßen aussehen:
Großartig, jetzt ist alles an Ort und Stelle und wir können beginnen, unsere Zellen vorzustellen.
UITableViewCell
Um 
UITableViewCell zu implementieren, müssen wir diese Klasse fast immer in Unterklassen unterteilen. Klicken Sie daher im Projektnavigator mit der rechten 
ContactCard Ordner 
UITableViewCell , dann auf "Neue Datei ...", dann auf "Cocoa Touch Class" und "Weiter".
Geben Sie "UITableViewCell" in das Feld "Unterklasse von:" ein, und Xcode füllt automatisch den Klassennamen "TableViewCell" aus. Geben Sie vor der automatischen Vervollständigung "ProfileView" ein, sodass der endgültige Name "ProfileInfoTableViewCell" lautet, und klicken Sie dann auf "Weiter" und "Erstellen". Löschen Sie die erstellten Methoden, da wir sie nicht benötigen. Wenn Sie möchten, können Sie zuerst die Beschreibungen lesen, um zu verstehen, warum wir sie derzeit nicht benötigen.
Wie bereits erwähnt, enthält unsere Zelle grundlegende Informationen, nämlich den Namen des Felds und seine Beschreibung. Daher benötigen wir Beschriftungen für diese Felder.
 lazy var titleLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Title" return label }() lazy var descriptionLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Description" label.textColor = .gray return label }() 
Und jetzt definieren wir den Initialisierer neu, damit die Zelle konfiguriert werden kann:
 override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(titleLabel) contentView.addSubview(descriptionLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 
In Bezug auf die Einschränkungen werden wir etwas anderes machen, aber dennoch sehr nützlich:
 override func updateConstraints() { let titleInsets = UIEdgeInsetsMake(16, 16, 0, 8) titleLabel.autoPinEdgesToSuperviewEdges(with: titleInsets, excludingEdge: .bottom) let descInsets = UIEdgeInsetsMake(0, 16, 4, 8) descriptionLabel.autoPinEdgesToSuperviewEdges(with: descInsets, excludingEdge: .top) descriptionLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 16) super.updateConstraints() } 
Hier beginnen wir mit 
UIEdgeInsets , um den Abstand um jedes Etikett 
UIEdgeInsets . Ein 
UIEdgeInsets Objekt kann mit der 
UIEdgeInsetsMake(top:, left:, bottom:, right:) werden. Zum Beispiel sagen wir für 
titleLabel dass die Obergrenze bei vier Punkten und rechts und links bei acht liegen soll. Der Boden ist uns egal, weil wir ihn ausschließen, da wir ihn oben am Beschreibungszeichen anbringen. Nehmen Sie sich eine Minute Zeit, um alle Einschränkungen in Ihrem Kopf zu lesen und zu visualisieren.
Ok, jetzt können wir Zellen in unserer Tabellenansicht zeichnen. 
ViewController.swift wir mit 
ViewController.swift und ändern die verzögerte Initialisierung unserer Tabellenansicht, um diese Klasse von Zellen in der Tabellenansicht zu registrieren und die Höhe für jede Zelle 
ViewController.swift .
 let profileInfoCellReuseIdentifier = "profileInfoCellReuseIdentifier" lazy var tableView: UITableView = { ... tableView.register(ProfileInfoTableViewCell.self, forCellReuseIdentifier: profileInfoCellReuseIdentifier) tableView.rowHeight = 68 return tableView }() 
Wir fügen auch eine Konstante für die Kennung der Zellwiederverwendung hinzu. Diese Kennung wird verwendet, um Zellen aus der Tabellenansicht zu entfernen, wenn sie angezeigt werden. Dies ist eine Optimierung, die verwendet werden kann (und sollte), um 
UITableView zu unterstützen, zuvor präsentierte Zellen wiederzuverwenden, um neuen Inhalt anzuzeigen, anstatt die neue Zelle von Grund auf neu zu zeichnen.
Lassen Sie mich Ihnen nun zeigen, wie Sie Zellen in einer Codezeile in der 
cellForRowAt Methode 
cellForRowAt :
 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: profileInfoCellReuseIdentifier, for: indexPath) as! ProfileInfoTableViewCell return cell } 
Hier informieren wir die Tabellenansicht über das Zurückziehen einer wiederverwendbaren Zelle aus der Warteschlange unter Verwendung der Kennung, unter der wir den Pfad zu der Zelle registriert haben, in der der Benutzer erscheinen soll. Dann zwingen wir die Zelle zu 
ProfileInfoTableViewCell , um auf ihre Eigenschaften zugreifen zu können, damit wir beispielsweise den Titel und die Beschreibung festlegen können. Dies kann folgendermaßen erfolgen:
 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { ... switch indexPath.row { case 0: cell.titleLabel.text = "Phone Number" cell.descriptionLabel.text = "+234567890" case 1: cell.titleLabel.text = "Email" cell.descriptionLabel.text = "john@doe.co" case 2: cell.titleLabel.text = "LinkedIn" cell.descriptionLabel.text = "www.linkedin.com/john-doe" default: break } return cell } 
Setzen 
numberOfRowsInSection nun 
numberOfRowsInSection auf "3" und starten Sie Ihre Anwendung.
Erstaunlich, oder?
Selbstleimungszellen
Möglicherweise und höchstwahrscheinlich wird es einen Fall geben, in dem Sie möchten, dass verschiedene Zellen gemäß den darin enthaltenen Informationen eine Höhe haben, die nicht im Voraus bekannt ist. Dazu benötigen Sie eine Tabellenansicht mit automatisch berechneten Abmessungen. Tatsächlich gibt es eine sehr einfache Möglichkeit, dies zu tun.
ProfileInfoTableViewCell Sie in 
ProfileInfoTableViewCell diese Zeile zur 
descriptionLabel des verzögerten Initialisierers hinzu.
 label.numberOfLines = 0 
ViewController Sie zum 
ViewController und fügen Sie diese beiden Zeilen zum Initialisierer der Tabellenansicht hinzu:
 lazy var tableView: UITableView = { ... tableView.estimatedRowHeight = 64 tableView.rowHeight = UITableViewAutomaticDimension return tableView }() 
Hier teilen wir der Tabellenansicht mit, dass die Zeilenhöhe basierend auf ihrem Inhalt einen automatisch berechneten Wert haben sollte.
Bezüglich der geschätzten Zeilenhöhe:
"Durch die Bereitstellung einer nichtnegativen Schätzung der Zeilenhöhe kann die Leistung beim Laden der Tabellenansicht verbessert werden." - Apple Docs
In 
ViewDidLoad wir die Tabellenansicht neu laden, damit diese Änderungen wirksam werden:
 override func viewDidLoad() { super.viewDidLoad() ... DispatchQueue.main.async { self.tableView.reloadData() } } 
cellForRow nun eine weitere Zelle hinzu, erhöhen Sie die Anzahl der Zeilen auf vier und fügen Sie 
cellForRow eine weitere 
switch cellForRow :
 case 3: cell.titleLabel.text = "Address" cell.descriptionLabel.text = "45, Walt Disney St.\n37485, Mickey Mouse State" 
Führen Sie nun die Anwendung aus, und sie sollte ungefähr so aussehen:
Fazit
Erstaunlich, oder? Als Erinnerung daran, warum wir unsere Benutzeroberfläche tatsächlich codieren, finden Sie hier einen vollständigen Blog-Beitrag unseres mobilen Teams darüber, warum wir 
in Instabug keine Storyboards verwenden .
Was Sie in zwei Teilen dieser Lektion getan haben:
- Die Datei main.storyboardaus Ihrem Projekt entfernt.
- Wir haben UIWindowprogrammgesteuert erstellt und ihm einenrootViewControllerzugewiesen.
- Erstellt verschiedene Elemente der Benutzeroberfläche im Code, z. B. Beschriftungen, Bildansichten, segmentierte Steuerelemente und Tabellenansichten mit ihren Zellen.
- Verschachtelte UINavigationBarin Ihrer Anwendung.
- Erstellt eine dynamische Größe UITableViewCell.