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:
upperView
die Größe unserer upperView
Ansicht, um sie an die Höhe der Navigationsleiste upperView
.- Setzen Sie die Eigenschaft
isTranslucent
der Navigationsleiste auf false
. 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.storyboard
aus Ihrem Projekt entfernt. - Wir haben
UIWindow
programmgesteuert erstellt und ihm einen rootViewController
zugewiesen. - Erstellt verschiedene Elemente der Benutzeroberfläche im Code, z. B. Beschriftungen, Bildansichten, segmentierte Steuerelemente und Tabellenansichten mit ihren Zellen.
- Verschachtelte
UINavigationBar
in Ihrer Anwendung. - Erstellt eine dynamische Größe
UITableViewCell
.