UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Gestalten der Benutzeroberfläche

Unsere App lädt nun zwar alle Namen der Sturm-Bilder, aber sie macht damit noch nichts Interessantes. Die Ausgabe in der Debug-Console ist nur hilfreich bei der Fehlersuche.

Unser nächstes Etappenziel soll deshalb sein, die Bilder aufzulisten damit Anwender sich eines auswählen können. UIKit – das Framework von Apple für iOS Benutzeroberflächen – hat eine Reihe von vordefinierten und den Anwendern vertrauten Oberflächenelementen auf denen wir aufbauen können.

Bei dieser App entscheiden wir uns für den UITableViewController als das zentrale Element. Der basiert auf dem UIViewController – Apple’s einfachster Anzeige, fügt allerdings die Anzeige von Daten in Zeilen hinzu, durch die man scrollen kann und einzelne auswählen. Das kennst Du bereits von den Standard-Apps Einstellungen, Mail, Notizen und vielen mehr. Diese Klasse ist mächtig, flexible einsetzbar und doch extrem schnell, also ist es kein Wunder, daß sie von so vielen Apps eingesetzt wird.

Auch unsere bereits vorhandene Anzeige-Klasse ViewController basierte auf dem UIViewController, aber nun werden wir es auf den UITableViewController umbauen. Das geht recht einfach, aber dazu müssen wir einen neuen Bereich von Xcode kennenlernen, den Interface Builder.

Dazu gleich mehr. Erst einmal müssen wir eine kleine Anpassung an ViewController.swift vornehmen. Suche diese Zeile:

class ViewController: UIViewController {

In der hatten wir unsere Anzeige-Klasse ViewController erzeugt und von Apple’s Basis-Klasse UIViewController abgeleitet. Ändere das nun um in:

class ViewController: UITableViewController {

Das war eine kleine, aber wichtige Änderung. Nun erbt ViewController die Eigenschaften und Möglichkeiten von UITableViewController statt von UIViewController, wie wir gleich sehen werden.

Letztlich erbt auch der UITableViewController von der Basis-Klasse UIViewController – das nennen wir eine "Klassenhierachie". Ein übliches Vorgehen, um Funktionalitäten aufeinander aufbauen zu lassen.

Wir haben den Code für die Abstammung unseres ViewController von UITableViewController angepaßt, aber wir müssen das auch mit der Benutzeroberfläche machen. Man kann zwar Benutzeroberflächen komplett in Source-Code schreiben – und Viele machen das auch – aber überlicherweise verwendet man dazu einen graphischen Editor. Der heisst bei Xcode Interface Builder (kurz “IB”). Auch dort müssen wir die Änderung der Vererbung vornehmen.

Bisher haben wir uns nur auf den Source Code in der Datei ViewController.swift konzentriert. Verwende nun bitte den Projekt-Navigator (die linke Übersicht in Xcode) um die Datei Main.storyboard auszuwählen. Storyboard ist die Bezeichnung für Dateien, in denen Benutzeroberflächen abgelegt werden, die einerseits mit dem Editor Interface Builder geändert werden können und andererseits von unserer App zum Vorbereiten des UI (User Interface) geladen werden.

Das Anwählen von Main.storyboard öffnet gleichzeitig die graphische Darstellung des IB und Du solltest das Folgende sehen können:

Die Vorlage für eine Single View App stellt Dir eine große weisse Fläche zur Verfügung.

Genau diese Fläche sehen wir auch, wenn unsere App läuft. Diese Leere könntest Du durch Einfügen und Platzieren weiterer Elemente füllen. Aber das werden wir nicht tun, sondern sie statt dessen löschen.

Der beste Weg um Elemente im Interface Builder anzeigen, auswählen, ändern zu können ist über die Dokumentübersicht (document outline). Es kann aber gut sein, daß Du die gerade nicht sehen kannst. Öffne das Editor-Menü und wähle dort [Show Document Outline]. Es ist wohl die dritte Option von oben. Falls dort [Hide Document Outline] steht, war es bereits sichtbar.

Die Dokumentübersicht zeigt Dir alle Komponenten an, die von irgendeiner Ansicht in Deiner App verwendet werden. Dort findest Du auch “View Controller Scene”. Bitte wähle das aus und lösche es mit der Rück-Taste (Backspace) Deiner Tastatur (beim Mac liegt die über der Eingabe/Return-Taste).

Anstelle des langweiligen UIViewController von eben, wollen wir den schicken UITableViewController, passend zu unserer Code-Änderung. Zum Anlegen öffnen wir die Element-Bibliothek (object library) mit der Tastenkombination Cmd+Umschalt+L. Wenn Du dazu lieber das Menü verwendest: View > Libraries > Show Library

Das neue Fenster schwebt nun über Xcode und enthält zahlreiche Komponenten, die Du von hier in das Storyboard hineinziehen und nach Herzenslust arrangieren kannst. Es werden sehr viele angezeigt, deshalb empfiehlt es sich durch Eintippen der Anfangsbuchstaben im Eingabefeld "Filter" die Auswahl namentlich einzuschränken.

Tipp: Wenn Du möchtest, daß die Biblothek nach Deinem Platzieren noch offen bleibt, dann öffne sie beim nächsten Mal mit Alt+Cmd+Umschalt+L (also zusätzlich Alt gedrückt halten). Dann bleibt sie offen, verschieb- und in ihrer Größe anpaßbar.

Was wir gerade suchen, nennt sich Table View Controller. Wenn Du "table" in das Filterfeld eintippst, werden nur noch Table View Controller, Table View, und Table View Cell gelistet.

Wähle hier Table View Controller - das mit dem gelbem Hintergrund im Icon - und ziehe es mit weiterhin gedrückter Maustaste (oder Trackpad) in die nun frei gewordene Fläche in der unser bisheriger view controller war. Wenn Du losläßt, fällt der neue Table View Controller auf die Storyboard-Gestaltungsfläche und Deine Anzeige sollte nun so aussehen:

So sollte es nach dem Wechsel der View Controller aussehen.

Abschliessende Arbeiten an der Benutzeroberfläche

Wir müssen noch ein paar kleine Anpassungen vornehmen:

Erst einmal müssen wir Xcode klarmachen, daß dieser neue Table View Controller im Storyboard der selbe ist, den wir in unserem Code in ViewController.swift erstellt hatten. Um das zu tun, drücke Alt+Cmd+3, was den Identitäts Inspektor öffnet (identity inspector). Das geht auch über das Menü: View > Utilities > Show Identity Inspector. Suche dort nach der Überschrift "Class". Dort steht derzeit UITableViewController als hellgrauer Text, aber mit dem Pfeil rechts daneben kannst Du eine der Unterklassen auswählen, die Xcode im Code gefunden hat – wähle ViewController.

Als Zweites sagen wir Xcode, daß dieser neue Table View Controller gleich beim Start der App angezeigt werden soll: Dazu drücke Alt+Cmd+4, was den Attributes Inspector öffnet (oder per Menü: View > Utilities > Show Attributes Inspector). Suche dort die Option für “Is Initial View Controller” und stelle sicher, daß sie aktiviert ist.

Drittens möchte ich, daß in der Dokumentübersicht in den neu angelegten "Table View Container" hineinschaust. Dort sollte ein "Table View" und darunter ein Eintrag "Cell" sein. Eine solche Table View Cell zeigt die Daten einer Tabellenzeile an und wir wollen dort jeweils einen Bildnamen anzeigen.

Wähle "Cell" aus und gib im Attributes Inspector den Namen “Picture” in das Feld für Identifier. Wo wir hier schon einmal sind – ändere den oben eingestellten Style von Custom auf Basic.

Zuletzt betten wir den ViewController noch in etwas Weiteres ein. Etwas, was nicht unbedingt sein muß, man aber sehr oft in Apps wiederfindet. Man nennt es einen Navigation Controller und Du erkennst es an einem dünnen grauen Balken und der seitwärtigen Animation beim Wechseln von Ansichten.

Um das zu tun, wähle mit Edit-Menü > Embed In > Navigation Controller. Der Interface Builder wird unseren bereits vorhandenen ViewController nach rechts schieben mit einem simulatierten grauen Balken über der Tabelle anzeigen und die vorhin einstellte Eigenschaft “Is Initial View Controller” auf den Navigation Controller übertragen.

Jetzt lohnt es sich, einen Blick auf unsere Änderungen zu werfen: Drücke Cmd+R oder den Play-Knopf und sei stolz auf das Erreichte. Statt der nackten Fläche sehen wir nun eine leere Tabelle, die beim Scrollen mit der Maus etwas an den Grenzen nachwippt. Wir sehen oben auch das Grau des Navigations-Balkens, was später noch relevant werden wird.

Viele Zeilen anzeigen

Im nächsten Schritt bringen wir unserer Tabelle bei, viele Bildnamen anzuzeigen. Zwar stellt Apple’s UITableViewController Klasse viele hilfreiche Eigenschaften zur Verfügung, aber am Anfang zeigt er Nichts an.

Unser ViewController erbt von UITableViewController und wir brauchen nur die Methoden überschreiben und damit sein Verhalten anpassen, die sinnvoll erscheinen. Die anderen Eigenschaften belassen wir auf ihren sinnvollen Grundeinstellungen.

Um die Tabelle etwas anzeigen zu lassen, müssen wir zwei Verhalten anpassen: Wie viele Zeilen sollen angezeigt werden und was jede der Zeilen beinhalten soll. Das machen wir durch Überschreiben von zwei besonders benannten Methoden. Dem Swift-Anfänger wird die nun folgende Schreibweise ungewohnt vorkommen. Ich erkläre Alles im Folgenden.

Wir fangen an mit der Methode für die Zeilenanzahl. Füge das Folgende hinter den Funktions-Block von viewDidLoad() ein, also unter dessen schliessende, geschweifte Klammer. Während Du “numberof” eingibts, kommt Dir die Code-Vervollständigung von Xcode zur Hilfe:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return pictures.count
}

In dieser Methode kommt der Begriff “table view” gleich drei mal vor - verwirrend, nicht wahr? Laßt uns das genauer betrachten:

  • Das Schlüsselwort override bedeutet daß es diese Methode bereits in der Oberklasse gibt, wir sie aber ersetzen/überschreiben wollen. Wenn wir das nicht tun, würde weiterhin die Methode der Oberklasse aufgerufen werden, die antwortet, es gäbe keine Zeilen.
  • Das Schlüsselwort func leitet sowhl eine Funktion, wie auch eine Methode ein. Der einzige Unterschied in Swift zwischen den beiden ist, daß Methoden zu einer Klasse (class) gehören.
  • Dann folgt der Methodenname: tableView in der Schreibweise tableView(_ Das erscheint erst einmal ungewohnt, aber Apple stellt damit sicher, daß alle Angaben die wir übergeben auch sinnvoll benannt werden. In diesem Falle ist das erste was übergeben wird, der TableView der die Aktion verursacht. Ein TableView, wie Du wohl bereits vermutet hast, ist das scrollbare Ding, das unsere Bildernamen enthalten wird – ein fundamentaler Bestandteil des iOS Benutzungskonzeptes.
  • Wie versprochen wird dann der erste Parameter genauer beschrieben tableView: UITableView. Der erste Teil ist die Bezeichnung unter der wir in der Methode auf den Wert zugreifen können. Der Zweite ist dessen Datentyp, also um was es sich handelt.
  • Dann kommt der wichtigste Teil: numberOfRowsInSection section: Int. Der beschreibt, was die Methode eigentlich macht. Anhand des Methoden-Names erkennen wir daß es um einen TableView geht. Aber mit numberOfRowsInSection wird die eigentliche Aufgabe der Methode deutlich: Diese Methode wird von iOS gerufen um zu fragen, wieviele Einträge die Tabelle hat. Der Parameter section wird gebraucht, weil man TableViews auch in Sektionen untergliedern kann. So wie nach Anfangsbuchstaben in den Kontakten. Wir brauchen nur eine Sektion, können den Wert also ignorieren. Das Schlüsselwort Int besagt, daß uns eine Ganzzahl übergeben wird.
  • Ganz hinten erscheint -> Int: Als Rückgabewert wird eine Ganzzahl erwartet. In unserem Fall, die Anzahl der Bildernamen.

Eine Sache ist noch nicht richtig erklärt worden: Das _ beim ersten Parameter. Dieser Unterstrich ändert die Art und Weise, wie diese Methode aufgerufen wird. Um den Unterschied zu demonstrieren, zeige ich Euch eine normale Funktion/Methode:

func machEtwas(ding: String) {
    // mach etwas mit "ding"
}

Die Funktion hat einen leeren Block, aber uns interessiert hier nur der Aufruf. Und der sieht derzeit so aus:

machEtwas(ding: "Hello")

Wir müssen den Namensteil vor dem ersten Parameters ausschreiben, wenn wir die Methode aufrufen. Das ist eine hilfreiche Besonderheit von Swift. Namensteile vor den Parametern machen den Code lesbarer, oft selbsterklärend. Nur, wenn der Methodenname selbst schon etwas über den ersten Paramter aussagt, stört das eher. Für solche Fälle ist der Unterstrich (Underscrore) gedacht:

func machEtwas(_ ding: String) {
    // mach etwas mit "ding"
}

Es besagt: "beim Aufruf, will ich den Namensteil vor dem ersten Parameter nicht nennen müssen". Und weiterhin kann ich innerhalb der Methode den String unter dem Namen "ding" verwenden.

Zurück zu unserer UITableView Methode. Ohne den Unterstrich müssten wir sie so aufrufen:

tableView(tableView: someTableView)

Mit der Unterstrich-Schreibweise geht es aber sinnvoller so:

tableView(someTableView)

Ich behaupte nicht, daß das einfach zu verstehen ist, aber bitte glaube mir: Nach ein paar Stunden mit Swift wird es Dir leicht von der Hand gehen.

Was Du Dir merken mußt ist Folgendes: In Swift umfaßt der Methoden-Name auch weitere Namensteile vor den Parametern. Die ohne Namen werden als _ deklariert und im Methoden-Namen geführt. Der Compiler kennt unsere Methode als tableView(_:numberOfRowsInSection:) – sehr lang, weshalb man dann oft nur den wichtigen Teil benennt und z.B. sagt: "in der numberOfRowsInSection Methode".

Wir schrieben in der Methode nur eine einzige Zeile Code, und zwar return pictures.count. Das bedeutet "gib die Anzahl der Bildnamen in unserem Array zurück", womit wir gleichviele Tabellen-Zeilen anfordern.

Tabellenzellen bereitstellen

Die zweite der n´benötigten Methoden bestimmt, was in unseren Tabellenzeilen stehen soll und sie folgt einem ähnlichen Namen-Schema. Füge folgenden Code ein:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)
    cell.textLabel?.text = pictures[indexPath.row]
    return cell
}

Lass' uns auch das unter die Lupe nehmen:

Der Anfang override func tableView(_ tableView: UITableView ist der selbe wie bei unserer vorherigen Methode. Auch hier kann ich den ersten Parameternamen beim Aufruf weglassen. Der Teil danach cellForRowAt indexPath: IndexPath unterscheidet sich. Diese Methode wird kurz cellForRowAt genannt und zum Füllen einer Tabellenzeile aufgerufen. Um welchen Eintrag es sich handelt, wird im Parameter indexPathvom Datentyp IndexPath übergeben. Darin sind sowohl die Sektions-Nummer, wie auch die Nummer der Zeile beinhaltet. Da wir nur eine Sektion nutzen, können wir den ersten Teil ignorieren und nur die Zeilenummer verwenden.

Abschliessend bedeutet das -> UITableViewCell, daß unsere Methode eine Tabellenzelle zurückgeben soll. Vielleicht erinnerst Du Dich, wir hatten eine solche Zelle im Interface Builder angelegt und ihr den Identifier "Picture" gegeben. Die werden wir hier nutzen. Und wir nutzen etwas von der Magie, die uns iOS in UIKit bereitstellt: Die Wiederverwendung von Tabellenzellen.

Wenn Du Dir eine typische App anschaust, wie die Einstellungen, fällt auf, daß nie mehr als etwa ein Dutzend Tabellenzeilen gleichzeitig zu sehen sind. Um CPU-Arbeit und Speicher zu sparen, erzeugt iOS nur so viele Tabellenzeilen wie nötig. Sobald eine vom Bildschirm verschwindet, wird sie auf einen Wiederverwendungs-Stapel gelegt, bereit um mit Inhalten gefüllt zu werden, die neu auftauchen. Auf diese Weise können wir schnell durch Hunderte von Einträgen scrollen, ohne daß iOS zu viele Grafik-Resourcen benötigt. Die werden einfach recycled.

Dieses Verhalten ist integraler Bestandteil von iOS und wir verwenden es in dieser Code-Zeile:

let cell = tableView.dequeueReusableCell(withIdentifier: "Picture", for: indexPath)

Damit wird eine neue Konstante namens cell mit einer recycleten Zelle der Tabelle erzeugt. Wir geben als Identifier des Zellen-Typs das selbe wie vorher im IB an: “Picture”. Und wir reichen den Index-Pfad durch; das wird intern von der Tabelle benötigt.

Damit erhalten wir Zugriff auf eine TableViewCell, die wir zur Anzeige unserer Angaben auf dem Bildschirm nutzen können. Du kannst auch Deine eigenen Zellen entwerfen, wenn benötigt (mehr dazu später), aber derzeit kommen wir mit dem Standard-Stil aus, der einfach einen Text darstellt. Den setzen wir in unserer zweiten Zeile Code auf einen der Bildnamen in unserem Array:

cell.textLabel?.text = pictures[indexPath.row]

Die Zelle cell besitzt eine Property namens textLabel, aber diese ist optional: also es kann einen Text Label gehen, oder nicht – z.B. wenn Du Deinen eigene Zelle entworfen hast. Anstelle überall über ein if abzufragen, ob ein Label vorhanden ist oder nicht, stellt uns Swift die ?-Schreibweise bereit. Sie übernimmt diese Prüfung und führt die rechts angegebene Zuweisung nur aus, wenn ein textLabel vorhanden ist.

Wir wollen den Text auf den jewiligen Bildnamen in unserem pitures Array setzten, und genau das macht unser Code. In indexPath.row wird die Zeilen-Nummer stehen, die wir befüllen sollen, also greifen wir darüber auch in das passende Array-Element. Beide Systeme sind ab der Zahl 0 indiziert.

Die letzte Code-Zeile der Methode lautet: return cell. Erinnere Dich, die Methode wurde so deklariert, daß eine TableViewCell zurückgegeben wird. Wir geben daher die soeben recycelte und mit Text befüllte zurück.

Mit diesen beiden recht überschaubaren Methoden kannst Du den Code erneut ablaufen lassen und sehen wie es aussieht. Wenn Alle geklappt hat, sollten 10 Zeilen mit unterschiedlichen Namen angezeigt werden. Wenn Du einen anwählst, wir er einfach nur ausgrauen. Lass´ uns das als Nächstes in etwas Sinnvolles umbauen…

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 1.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.