Sie haben bereits einige Elemente grafischer Benutzeroberflächen kennengelernt, nämlich Buttons, Slider und Labels. Und Sie wissen, wie Sie diese auf einem Bildschirm platzieren und auf Benutzeraktionen reagieren lassen können. In diesem Kapitel werden Sie ein weiteres wichtiges Element kennenlernen, das iOS besonders bekannt gemacht hat: nämlich die Tabelle. Außerdem werden Sie lernen, wie Sie grafische Oberflächen über mehrere Bildschirme verteilen.
Tabellen sind etwas Besonderes unter iOS: Selbst auf den ersten iPhones im Jahre 2007 konnten Tabellen sehr groß sein und sprichwörtlich Tausende Zeilen enthalten. Und selbst auf diesen Geräten sind die Tabellen sehr flüssig und natürlich gewesen. Selbst heutzutage können die besten Geräte der Konkurrenz immer noch nicht einen so schönen und natürlichen Effekt nachbauen, wie ihn schon die erste iPhone-Generation beherrschte.
Um Tabellen selbst auf derartig leistungsschwachen Geräten wie der ersten iPhone-Generation so vielseitig zu gestalten, haben sich die Apple-Ingenieure einige sehr clevere Tricks ausgedacht, um das zu erreichen. Daher funktionieren Tabellen auch ein bisschen anders als die Objekte, die Sie bisher kennengelernt haben. Tabellen entscheiden selbst, welche Informationen sie benötigen, und fragen dann Ihr Programm nach exakt den Informationen, die die Tabelle braucht. »Unter der Haube« kümmern sich Tabellen danach um den ganzen Rest sie wissen, wie sie sich darstellen, wie sie auf Benutzereingaben reagieren und wie sie so schön flüssig und ordentlich scrollen müssen. Als Entwickler müssen Sie sich nur darum kümmern, die Informationen vorzuhalten und auf Verlangen preiszugeben.
In der Praxis müssen Sie daher einige Methoden implementieren, die die richtigen Informationen bereitstellen. Diese sind im Einzelnen:
numberOfSectionsInTableView()
mit einem Parameter namens tableView
. Hier müssen Sie eine Zahl zurückgeben, die die Anzahl der Abschnitte der Tabelle angibt.tableView
mit zwei Parametern: Der erste ist wieder tableView
, der zweite numberOfRowsInSection
. Hier geben Sie eine Zahl zurück, die die Anzahl der Zeilen in einem bestimmten Abschnitt angibt.tableView
mit zwei Parametern: Der erste ist tableView
, der zweite cellForRowAtIndexPath
. In dieser Methode geben Sie eine Tabellenzelle zurück, die in einem bestimmten Abschnitt in einer bestimmten Zeile angezeigt werden soll. Eine Tabellenzelle ist ein Objekt, das UITableViewCell
als Basisklasse hat. Dies hört sich ein bisschen komplizierter an, als es ist. Oft kommen Sie mit der Vorlage von Xcode aus und müssen nur den Text geeignet anpassen.Sie können noch einige weitere Methoden angeben, aber wenn Sie sich nur um diese drei kümmern, dann bekommen Sie bereits eine voll funktionsfähige Tabelle.
Abbildung 13.1 Das Prinzip der Datenquelle für eine Tabelle
Diese Art und Weise, eine Tabelle zu beschreiben, unterscheidet sich deutlich von den Eigenschaften in Objekten, die Sie bisher kennengelernt haben. Sie stellen ein Objekt zur Verfügung, das von der Tabelle benutzt wird, um Informationen aufzurufen. Das Objekt ist daher eine Quelle für Daten für die Tabelle, auf Englisch Data Source genannt. Auf diesem Weg stellen Sie sicher, dass nur die Daten, die die Tabelle tatsächlich benötigt, abgerufen und im Speicher gehalten werden. Dadurch stellen Sie auch riesige Tabellen dar, die erst nach und nach berechnet werden müssen. Darin liegt das Geheimnis, warum Apple schon im Jahre 2007 mit einem sehr schwachen Mobiltelefon riesige Tabellen auf den Bildschirm zaubern konnte.
Abbildung 13.1 stellt diese Abhängigkeit grafisch dar. Sie kümmern sich darum, die Informationen bereitzustellen, und die Tabelle kümmert sich um den ganzen Rest.
Jetzt setzen Sie das FizzBuzz-Programmbeispiel aus Abschnitt 5.1 ebenfalls mit einer iPhone-Tabelle um. Glücklicherweise bietet Xcode bereits eine Vorlage für eine Tabellenanwendung, die so genannte Master-Detail Application.
Dafür gehen Sie folgendermaßen vor:
Abbildung 13.2 Die Auswahl der Projektvorlage für eine Tabelle, die »Master-Detail Application«
Abbildung 13.3 Die Konfiguration des Projekts »FizzBuzzTabelle« nach der Vorlage »Single View Application« in Xcode
Sie werden jetzt feststellen, dass die Vorlage ein bisschen umfangreicher ist als die bisherigen Vorlagen. In der linken Spalte von Xcode finden sich zwei Swift-Dateien, der MasterViewController.swift
sowie der DetailViewController.swift
. Wenn Sie die Datei Main.storyboard
auswählen, erscheint wieder der grafische Editor, allerdings enthält er diesmal fünf verschiedene iPhone-Bildschirme (siehe auch Abbildung 13.4). Leider kann es passieren, dass die Grafik jetzt so groß ist, dass sie nicht mehr vollständig zu sehen ist. Daher läßt sich das Storyboard mit den Tasten +
sowie
+
zoomen. Alternativ können Sie auch einen Rechtsklick auf den Hintergrund der mittleren Spalte machen und im Kontextmenü direkt die Zoomstufe auswählen. Einige Aufgaben lassen sich nur in der maximalen Vergrößerungsstufe erledigen, sodass Sie für die folgenden Arbeitsschritte zur maximalen Vergrößerung zoomen sollten.
Abbildung 13.4 Die Datei »Main.storyboard« im grafischen Editor von Xcode in der Vorlage »Master-Detail Application«
Diese Vorlage kann mehr als die »Single View Application«, die Sie vorher verwendet haben. Wenn Sie die Vorlage mit +
starten, so sehen Sie einen iPhone-Bildschirm im Simulator mit einer Tabelle. Mit dem +-Zeichen in der rechten oberen Ecke des iPhone-Bildschirms können Sie neue Zeilen hinzufügen, die das aktuelle Datum und die Uhrzeit enthalten. Wenn Sie auf eine Zeile tippen, so erscheint von links ein neuer Bildschirm, in dem der Eintrag zentriert ist. Von diesem Bildschirm kommen Sie wieder durch Drücken des < MASTER-Buttons in der linken oberen Ecke zurück zur Tabelle. Außerdem können Sie mit Hilfe des Buttons links oberhalb der Tabelle einen Editiermodus einschalten, wo Sie Zeilen löschen können.
Diese Vorlage besteht also aus zwei verschiedenen Bildschirmen: einmal der Tabelle und dann einem weiteren Bildschirm, den Sie erreichen, wenn Sie auf eine Tabellenzeile klicken. Aus diesem Grund nennt sich die Vorlage »Master-Detail«, denn der erste Bildschirm (die Tabelle) enthält eine Übersicht über alle Daten (er ist der »Master«) und der zweite zeigt Detailinformationen an (er entspricht dem »Detail«). Wie genau dieser Übergang zwischen den beiden Bildschirmen funktioniert, erkläre ich etwas später in diesem Kapitel. Zunächst möchte ich mich auf die Tabelle konzentrieren.
Das Objekt, das als Datenquelle für die Tabelle dient, nennt sich MasterViewController
. Wenn Sie die Datei MasterViewController.swift
auswählen, so findet sich bereits ziemlich viel Programmtext darin. Dazu gehören auch die drei Methoden, die die Größe der Tabelle sowie eine einzelne Tabellenzelle zurückgeben. Sie finden sich ganz am Ende ab der Zeile, die mit
// MARK: -- Table View
markiert ist. Die ersten drei Methoden sind dann auch die gesuchten drei Methoden, die als Datenquelle dienen. Die anderen beiden Methoden sind zum Entferne und Erzeugen von Tabellenzeilen erforderlich. Für das FizzBuzz-Beispiel benötigen Sie im Moment lediglich eine statische Tabelle mit einem einzelnen Abschnitt und 100 Zeilen. Die Tabelle soll nicht editierbar sein (d.h., der Benutzer soll sie nicht verändern können).
// MARK: -- Table View
/// Liefere die Zahl der Tabellenabschnitte.
override func numberOfSectionsInTableView(tableView: UITableView) --> Int {
// Diese Tabelle hat nur einen Abschnitt.
return 1
}
/// Liefere die Zahl der Tabellenzeilen in einem Abschnitt.
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) --> Int {
// Die Tabelle hat 100 Zeilen.
return 100
}
/// Liefere eine Tabellenzelle für eine bestimmte Stelle.
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -->
UITableViewCell {
// Erzeuge eine neue Zelle.
let cell = tableView.dequeueReusableCellWithIdentifier("Cell",
forIndexPath: indexPath)
// Setze den Text auf die Zeilennummer oder
// "Fizz", "Buzz" oder "FizzBuzz".
let fizzBuzzText = fizzBuzz(indexPath.row + 1)
cell.textLabel!.text = fizzBuzzText
return cell
}
Listing 13.1 Die notwendigen Methoden der Datenquelle einer Tabelle am Ende der Datei »MasterViewController«
Zu diesem Zweck ersetzen Sie die Methoden der Tabellendatenquelle durch die in Listing 13.1 gezeigten. Diese Methoden sind offensichtlich ein bisschen einfacher als die Methoden der Vorlage. Hier sind die folgenden Punkte wichtig:
indexPath
vom Typ NSIndexPath
. Dieser kombiniert den Abschnitt und die Tabellenzeile, die Sie liefern sollen. Den Abschnitt bekommen Sie durch indexPath.section
und die Zeile durch indexPath.row
. Da es nur einen Abschnitt gibt, ist hier lediglich die Zeile interessant. Zunächst jedoch müssen Sie eine Zelle erzeugen. Glücklicherweise kann die Tabelle das selbstständig, indem Sie die Methode dequeueReusableCellWithIdentifier
auf der Tabelle aufrufen. Die Parameter sind einmal ein eindeutiger String sowie der indexPath
. Eine Zelle kann dann mit einem Text versehen werden, indem Sie die Eigenschaft textLabel.text
auf einen String
setzen. Diesen String erzeugen Sie mit der fizzBuzz
-Funktion aus Listing 5.1 in Abschnitt 5.1.3.fizzBuzz
-Funktion müssen Sie noch in das aktuelle Listing einfügen, beispielsweise ganz ans Ende hinter die Datenquelle.indexPath.row
bei 0 anfängt zu zählen. Dadurch würde die Tabelle mit »FizzBuzz« anfangen und mit »99« aufhören. Weil das ein bisschen unschön ist, addiere ich eins zu indexPath.row
.tableView
mit Parameter canEditRowAtIndexPath
wird von der Tabelle aufgerufen, wenn die Tabelle bearbeitet wird. Das passiert, wenn Zeilen verschoben oder gelöscht werden sollen. Da Sie den EDIT-Button aus der Methode viewDidLoad
entfernt haben, wird diese Methode im Moment auch nicht mehr benötigt.tableView
mit den Parametern commitEditingStyle
und forRowAtIndexPath
führt eine Bearbeitung durch den Benutzer aus, das heißt, verschiebt oder löscht Zellen und so weiter. In diesem Kapitel wird sie daher nicht benötigt.Besonders wichtig ist nun die Methode viewDidLoad
. Dies ist wieder der Einstiegspunkt, wo Sie Ihren eigenen Initialisierungscode schreiben. Im Gegensatz zu einem Kommandozeilenprogrammen läuft das Programm aber weiter, nachdem Sie diese Methode verlassen haben. In der Tat läuft das Programm sehr eigenverantwortlich: Sie geben in dieser Klasse lediglich die Daten der Tabelle an und iOS lässt das Programm selbstständig so lange laufen, bis der Benutzer es beendet. Es gibt in der Regel in iOS-Programmen kein Programmende, das der Programmierer vorgibt.
Im Moment steht in viewDidLoad
bereits einiger Programmtext, der anscheinend mit Buttons zu tun hat. In der Tat fügt dieser Programmtext die Buttons in der Navigationsleiste der Tabelle ein den +-Button in der rechten oberen Ecke und den EDIT-Button in der linken oberen Ecke. Ich empfehle Ihnen, diese Dinge zunächst wegzulassen und stattdessen lediglich die Eigenschaft self.fizzBuzzTabelle
zu setzen, um damit ganz bequem die Tabelle zu »füttern«.
Desweiteren gibt es noch eine Reihe weiterer Methoden, die Sie allerdings größenteils löschen oder auskommentieren können:
viewWillAppear
wird aufgerufen, sobald der Bildschirm mit der Tabelle erscheint. Ich empfehle Ihnen, diese zunächst unverändert zu lassen. Die Methode
didReceiveMemoryWarning
wird aufgerufen, wenn der Speicher auf dem iPhone knapp wird. Für umfangreiche Apps könnten Sie hier Aufräumarbeiten beginnen. In diesem Fall ist das aber nicht notwendig, weil diese App keine großen Datenmenge hat, die sie aufräumen muss. Diese Methode können Sie daher löschen.
insertNewObject
wurde in viewDidLoad
als Aktion auf die Betätigung des Buttons in der Menüleiste definiert da ich empfohlen habe, diesen Teil zunächst zu löschen, kann diese Methode ebenfalls weg.
prepareForSegue
sorgt dafür, dass beim Antippen einer Tabellenzeile der neue Bildschirm korrekt konfiguriert wird. Darauf werde ich später näher eingehen, im Moment empfehle ich Ihnen, diese Methode auszukommentieren.
objects
am Anfang der Klasse löschen, da Sie die FizzBuzz-Einträge direkt berechnen.
Listing 13.2 zeigt, was außer den Datenquellenmethoden aus Listing 13.1 noch in der Klasse MasterViewController
übrig bleibt.
viewDidLoad
oder die ganzen tableView
-Methoden also Methoden, die Sie nicht selbst aufrufen, sondern für andere zur Verfügung stellen nennt man auch Callbacks. Für Callbacks ist es immer eine gute Idee, die Quelle anzugeben, selbst wenn sie »offensichtlich« ist./// Tabelle mit Einträgen des FizzBuzz--Spiels.
class MasterViewController: UITableViewController {
/// Der zweite View Controller.
var detailViewController: DetailViewController? = nil
/// Wird zu Beginn von iOS aufgerufen.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Setze den zweiten View Controller (die Detailseite).
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count--1] as!
UINavigationController).topViewController as? DetailViewController
}
}
// Wird von iOS aufgerufen, wenn der Bildschirm erscheint.
override func viewWillAppear(animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed
super.viewWillAppear(animated)
}
// Hier kommen noch die drei Datenquellenmethoden!
}
Listing 13.2 Programmtext vom Anfang der Klasse »MasterViewController«
Starten Sie jetzt das Programm erneut und vergewissern Sie sich, dass die Tabelle weiterhin korrekt funktioniert, nur jetzt mit den Einträgen von Fizzbuzz bevölkert wird.
Am Anfang der Methode viewDidLoad
gibt es einen Aufruf
super.viewDidLoad()
Dieser Aufruf bezeichnet ein Objekt namens super
. Dies ist ein besonderes Objekt, ähnlich wie self
.
Und zwar bedeutet super
, dass die Methode der Basisklasse aufgerufen werden soll, selbst dann, wenn Sie (wie in diesem Fall) eine eigene Methode bereitgestellt haben. Dadurch können Sie sicherstellen, dass eine Unterklasse auch dann funktioniert, wenn ihre Basisklasse möglicherweise einige komplizierte interne Aufgaben erledigt.
Insbesondere beim Überschreiben von Callbacks wie viewDidLoad
empfehle ich Ihnen, immer die entsprechende Methode von Apples Basisklasse aufzurufen, weil sonst Ihre Programme in späteren iOS-Versionen vielleicht nicht mehr richtig funktionieren!
Eine Besonderheit stellt in Swift hierbei die init
-Methode dar. In der Programmiersprache Objective-C war es notwendig, in einer abgeleiteten Klasse immer super.init()
der Basisklasse aufzurufen. Dies ist in Swift nicht mehr ausdrücklich notwendig, weil Swift schon von sich aus immer diesen Aufruf macht. Dadurch ist stets sichergestellt, dass ein Objekt korrekt eingerichtet wird, wenn Sie es erzeugen.
Wenn Sie die App aus dem Projekt »FizzBuzzTabelle« ausprobiert haben, so ist Ihnen vielleicht aufgefallen, dass das Auswählen einer einzelnen Tabellenzeile weiterhin zum Detailbildschirm führt. Allerdings steht dort im Moment nur ein Platzhaltertext. Diesen werden Sie nun durch den »richtigen« FizzBuzz-Text ersetzen.
Zunächst kehren Sie einmal zur Datei Main.storyboard
zurück dort haben Sie bereits fünf verschiedene iPhone-Bildschirme gesehen. In der App selbst haben Sie aber nur zwei gesehen. Wenn Sie die Tabelle auf dem Bildschirm des iPhone-Simulators sehen, so nimmt sie den ganzen Bildschirm ein. Diese Situation gibt es oft auf dem iPhone, sodass Apple für diese Situation eine eigene Klasse vorgesehen hat. Die Klasse, die sich um eine Tabelle auf dem gesamten Bildschirm kümmert, nennt sich sich UITableViewController
. Wenn Sie in MasterViewController.swift
schauen, so können Sie sehen, dass die Klasse MasterViewController
in der Tat die Basisklasse UITableViewController
besitzt.
Als Sie dann in der Tabelle eine Zeile auswählten, erschien ein neuer Bildschirm, der ein Label in der Bildschirmmitte besaß. Genauso wie für Tabellen hat Apple für andere Arten von Bildschirmen eine eigene Klasse vorgesehen. Diese Klasse nennt sich UIViewController
. In diesem Projekt hat die Klasse DetailViewController
den UIViewController
als Basisklasse.
UIViewController
als Basisklasse besitzt, für einen Bildschirm in einer iOS-App verantwortlich. Alle Ihre Klassen, die sich um eine solche Aufgabe kümmern, sollten daher von UIViewController
ableiten. Sie sollten UIViewController
niemals direkt verwenden, sondern immer von ihm ableiten und die fehlenden Funktionalitäten einfügen.UITableViewControllers
ist ein Spezialfall eines Bildschirms dieser zeigt genau eine Tabelle auf dem gesamten Bildschirm an. Aus diesem Grund hat UITableViewController
als Basisklasse seinerseits UIViewController
.Abbildung 13.5 Lesen und Setzen der Klasse eines View Controllers im Storyboard von Xcode
Wenn Sie es nicht schon erraten haben, so verrate ich Ihnen jetzt, dass diese Klasse DetailViewController
verantwortlich ist für die Darstellung des zweiten Bildschirms mit dem Label in der Mitte. Wenn Sie sich nun nicht nur auf mein Wort verlassen möchten, so finden Sie die »verantwortliche« Klasse folgendermaßen heraus: Zeigen Sie die rechte Spalte an und wählen Sie den dritten Reiter aus. Dann klicken Sie auf einen Bildschirm des Storyboards und der Name der Klasse wird unter CUSTOM CLASS als CLASS angezeigt, siehe Abbildung 13.5.
Die View Controller, die für die fünf verschiedenen Bildschirme verantwortlich sind, sind in Abbildung 13.6 zu sehen. Diese sind:
UISplitViewController
. Diese Klasse ist ausgegraut, das bedeutet, dass es eine Klasse von iOS ist.
UINavigationController
assoziiert sind.
MasterViewController
.
DetailViewController
.
Im Gegensatz zu einem UIViewController
ist es nicht notwendig, die Klassen UISplitViewController
und UINavigationController
für eigene Klassen abzuleiten. Diese Klassen gehören nicht zu »echten« Bildschirmseiten, sondern stehen symbolisch für bestimmte Funktionalitäten.
Abbildung 13.6 Klassen der verantwortlichen View Controller in der Datei »Main.storyboard«
Der UISplitViewController
zeigt zwei getrennte Bildschirme nach dem Master-Detail-Vorbild an. Dies ist beim iPhone noch nicht so wichtig, aber diese Vorlage lässt sich genauso für das iPad verwenden und dort sind beide Bildschirme direkt nebeneinander.
Der UINavigationController
ermöglicht es, mehrere Bildschirme zu »stapeln«. Viele Apps von Apple auf dem iPhone benutzen genau diese Technik der hierarchischen Navigation, zum Beispiel die Musik-App. Zunächst sehen Sie eine große Tabelle mit Ihren Playlisten. Wenn Sie eine Playliste auswählen, so erscheint ein neuer Bildschirm mit einer Tabelle aller darin enthaltenen Songs. Wenn Sie dort schließlich einen einzelnen Song wählen, so erscheint ein Bildschirm mit einem Abspielbalken und Abspielelementen. Sie können dann jederzeit in der Hierarchie wieder nach oben wandern und alle vorherigen Bildschirme aufsuchen. Dies wäre eine Möglichkeit, die Master-Detail-App zu schreiben und in früheren Versionen von Xcode hat Apple das auch genau so gemacht. In der Xcode-Version 7 geschieht das nun mit einem UISplitViewController
, allerdings funktioniert dieser nur für ein einzelnen Paar von Bildschirmseiten. Wenn Sie eine App wie die Apple Musik-App schreiben wollen, wo Sie beliebig tief in Ordner hinabsteigen möchten, so benötigen Sie weiterhin die UINavigationController
-Klassen. In dem Beispielprojekt in diesem Kapitel ist das nicht notwendig, aber für spätere Erweiterungen ist es auf jeden Fall da!
Das wirklich Interessante passiert nun beim Übergang zwischen beiden Bildschirmen. Solche Übergänge sind eigene Swift-Objekte und werden als Segue (sprich »Seg-Way«) bezeichnet. Diese werden im grafischen Editor mit den Kreisen und Pfeilen dargestellt. In Abbildung 13.4 gibt es derer fünf Stück. Die drei Bildschirme, die die Verwendung von UISplitViewController
und UINavigationController
symbolisieren besitzen keine echten Übergänge, sondern bei ihnen stehen die Pfeile für »enthält«. Diese Pfeile werden mit einem Symbol mit einem Schrägstrich dargestellt. Der »echte« Übergang liegt zwischen dem MasterViewController
und dem UINavigationController
, der vor dem DetailViewController
liegt. Wählen Sie diesen Übergang aus und wählen Sie in der rechten Spalte den vierten Reiter, vergleiche Abbildung 13.7.
Abbildung 13.7 Konfiguration des Übergangs zwischen dem Master- und dem Detail-Bildschirm
Jeder Segue hat einen Namen, dieser ist unter IDENTIFIER angegeben und ist in der Vorlage showDetail
. Jeder Segue, den Sie in Ihrer App verwenden, muss einen eindeutigen Namen haben. Das heißt, Sie müssen immer einen Namen vergeben und dürfen jeden Namen nur einmal verwenden. Die Klasse des Segue ist UIStoryboardSegue
. Sie geben ebenfalls die Art des Segue unter SEGUE an, im Moment ist dort SHOW DETAIL (E.G. REPLACE) eingestellt. Außerdem ist der Button ANIMATES ausgewählt. Die Art des Segue bestimmt, wie die neue Seite eingeblendet werden soll und erlaubt die Auswahl verschiedener Animationen bei bestimmten Arten von Übergängen. In einer Übungsaufgabe am Ende dieses Kapitels experimentieren Sie mit verschiedenen Übergängen.
Durch das Auswählen einer Tabellenzeile wird der Übergang zwischen beiden Bildschirmen ausgelöst. Das erkennen Sie auch daran, dass die Tabellenzeile blau umrandet gezeigt wird, wenn Sie den Übergang auswählen. Wenn der Übergang ausgelöst wird, passieren einige Sachen:
DetailViewController
wird erzeugt. Alle Verknüpfungen zwischen Eigenschaften und Aktionen der grafischen Objekte und DetailViewController
werden erstellt. Die Methode viewDidLoad
von DetailViewController
wird aufgerufen. Diese Schritte passieren in der Regel aber nur einmal beim ersten Aufruf.prepareForSegue
von MasterViewController
wird aufgerufen. Hier können Sie eigenen Programmtext eingeben, der vor dem eigentlichen Übergang ausgeführt wird.DetailViewController
eine Methode viewWillAppear
hat, wird diese aufgerufen. Solch eine Methode kann ganz nützlich sein, wenn Sie eine bestimmte Aktion regelmäßig machen wollen, bevor ein Bildschirm erscheint. Sie ist aber nicht zwingend erforderlich.Es passieren also zwei zusätzliche Schritte bei diesem Übergang und Sie haben die Möglichkeit, selbst Einfluss darauf zu nehmen, welche Eigenschaften im DetailViewController
gesetzt werden sollen, bevor dieser angezeigt wird. Dies passiert genau in der Methode prepareForSegue
. Zunächst einmal ist es aber ratsam, die Klasse DetailViewController
ein wenig näher zu betrachten.
/// Detailbildschirm, wird nach Antippen einer Tabellenzelle angezeigt.
class DetailViewController: UIViewController {
/// Label in der Mitte des Bildschirms.
@IBOutlet weak var detailDescriptionLabel: UILabel!
/// Eigenschaft für den angezeigten Text.
var detailItem: AnyObject? {
didSet {
// Das View wird immer angepasst, wenn sich der Text ändert.
self.configureView()
}
}
/// Passe das Label an.
func configureView() {
if let detail = self.detailItem {
// Wird ausgeführt, falls das detailItem nicht nil ist.
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Konfiguriere das Label beim erstmaligen Laden.
self.configureView()
}
}
Listing 13.3 Überarbeiteter Programmtext der Klasse »DetailViewController«
Ich habe den Programmtext von DetailViewController
etwas überarbeitet und Kommentare hinzugefügt, er ist in Listing 13.3 zu sehen. Hier gibt es einiges zu entdecken:
detailDescriptionLabel
. Diese Eigenschaft verweist auf das Label, das mit dem Storyboard verknüpft ist.Die dazugehörige Klasse nennt sich UILabel
und auf dieser können Sie den Text mit der Eigenschaft text
setzen.
detailItem
. Diese Eigenschaft ist dazu gedacht, von außen gesetzt zu werden, um den Text des Labels zu ändern.Wenn sich der Wert von detailItem
ändert, so wird mit Hilfe von didSet
nach der Änderung der Labeltext angepasst, siehe dazu auch den fortgeschrittenen Abschnitt 7.4.2.
detailItem
passiert in der Methode configureView
. Hier wird sichergestellt, dass der Inhalt nicht nil
ist und dann anschließend der Text des Labels gesetzt.viewDidLoad
setzt ebenfalls den Text des Labels, sobald das Label über das Storyboard geladen worden ist.Dies ist nicht überflüssig, denn es kann durchaus passieren, dass die grafische Oberfläche des Storyboards erst kurz vor der Anzeige geladen wird. Deswegen wird configureView
zweimal aufgerufen: einmal beim ersten Laden und dann bei jeder Änderung von detailItem
.
didReceiveMemoryWarning
habe ich wieder entfernt, weil diese Klasse nicht wirklich viel machen kann oder muss, um ihren Speicher aufzuräumen.
Nun ist es eigentlich nicht notwendig, zwei separate Eigenschaften zu haben: detailDescriptionLabel
einerseits und detailItem
andererseits. Warum setzt man nicht direkt detailDescriptionLabel.text
und spart sich einen Großteil dieser Klasse?
Der Grund ist, dass diese Klasse dadurch viel flexibler und mächtiger ist. Die Anzeige des detailItem
erfolgt im Moment durch ein Label. Alternativ könnte diese Klasse aber auch ein Bild laden, den Text vorlesen oder eine Email beginnen, in der dieser Text in der Betreffszeile steht. Der Aufrufer muss lediglich die Eigenschaft detailItem
setzen und braucht sich dann nicht mehr darum kümmern, was der DetailViewController
mit dieser Information macht. Würde der Aufrufer stattdessen lediglich einen Labeltext setzen, so müsste dieser Aufruf ständig angepasst werden, wenn sich die Funktionalität von DetailViewController
ändert. Das ist nun nicht mehr notwendig was mit dem Text in detailItem
passiert, ist alleine das Problem des DetailViewController
.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Wird nur beim "showDetail" Segue ausgeführt.
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
// Setze den controller als assoziierten DetailViewController.
let controller = (segue.destinationViewController as!
UINavigationController).topViewController as!
DetailViewController
// Setze die detailItem Eigenschaft des DetailViewControllers.
controller.detailItem = fizzBuzz(indexPath.row + 1)
// Konfiguriere den "Zurück"--Button.
controller.navigationItem.leftBarButtonItem =
self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Listing 13.4 Die Methode »prepareForSegue« in der Datei »MasterViewController.swift«
Nun fehlt noch der letzte Schritt: Nach der Auswahl einer Tabellenzelle muss der MasterViewController
kurz vorm Übergang vom ersten zum zweiten Bildschirm die Eigenschaft detailItem
auf dem DetailViewController
setzen. Genau für diesen Zweck ist die Methode prepareForSegue
gedacht diese wird vom Betriebssystem immer vor dem Ausführen eines Segue aufgerufen. Ich hatte Sie gebeten, diese Methode nicht zu entfernen, sondern zunächst nur auszukommentieren. Nun fügen Sie sie wieder ein und ersetzen Sie die Implementierung wie in Listing 13.4 gezeigt.
Hier passiert Folgendes:
UIStoryboardSegue
wird als Parameter segue
übergeben.
sender
.
Dieses Objekt ist im Moment nicht relevant.
segue.identifier
liefert den Namen, den Sie in der rechten Spalte in Abbildung 13.7 angegeben haben. Der Block der if
-Anweisung wird also nur ausgeführt, wenn der Name auch wirklich »showDetail« lautet. Dies ist hier egal, wird aber dann wichtig, wenn es von diesem Bildschirm ausgehend mehrere mögliche Übergänge geben würde, die zu unterschiedlichen anderen Bildschirmen führen könnten. Ich empfehle Ihnen immer, den Namen des Segue zu überprüfen.
indexPath
vom Typ NSIndexPath
enthält die aktuelle Tabellenzeile als indexPath.row
. Diese Variable bekommen Sie durch die Methode indexPathForSelectedRow
von der Tabelle self.tableView
.
DetailViewController
bekommen Sie über ein etwas komplizierteres Konstrukt: Zunächst ist segue.destinationController
der »Zielcontroller« des Übergangs. Dieser ist allerdings im Storyboard ein UINavigationController
. Um Ihren »richtigen« DetailViewController
zu bekommen, brauchen Sie die Eigenschaft topViewController
des UINavigationController
das ist der View Controller, der angezeigt wird (und in diesem Fall auch der einzige View Controller des Navigation Controllers).Der gesuchte DetailViewController
wird nun in der Konstanten controller
gespeichert.
indexPath.row
und die Zählung fängt wieder bei 0 an. Nun setzen Sie auf dem controller
-Objekt die Eigenschaft detailItem
auf den relevanten fizzBuzz
-Text.indexPath.row
ebenfalls bei 0 anfängt und Sie beim Aufruf von fizzBuzz
daher eine 1 addieren müssen.Dadurch haben Sie erreicht, dass im Label des Detail-Bildschirms genau der Text der ausgewählten Tabellenzeile angezeigt wird. Vergewissern Sie sich, dass alles funktioniert, indem Sie das Programm mit +
starten und verschiedene Zeilen in der Tabelle auswählen.
Diese Erklärungen waren jetzt in der Tat recht umfangreich. Obwohl es nur um den Wechsel zwischen zwei Bildschirmen gegangen ist, passiert doch eine ganze Menge dabei und ich musste eine ganze Menge erklären, um die Hintergründe und Zusammenhänge verständlich zu machen. Aber sobald Sie diese Grundlagen verstanden haben, brauchen Sie nur wenige Mausklicks und nur wenig Programmtext, damit Sie recht komplizierte Sachen vorführbereit haben:
Gäbe es nicht das Hilfsmittel des grafischen Editors und die weitgehende Automatisierung der Übergänge, so hätten Sie für das gleiche Ergebnis wesentlich mehr Aufwand und Mühe betreiben müssen! Dies ist gerade eine der großen Stärken von Xcode, denn es ermöglicht Ihnen, auch sehr komplexe Vorgänge mit nur wenigen Handgriffen zu erledigen, wenn Sie erst mal die Grundlagen verstanden haben.
In diesem Kapitel lernen Sie ein wichtiges Konzept kennen: Das Konzept von Model-View-Controller, das grundlegend für grafische Benutzeroberflächen ist. Auf Deutsch lassen sich diese Konzepte am besten durch »Modell-Präsentation-Steuerung« übersetzen. Ursprünglich wurden diese Konzepte im Vorgänger von MacOS X entwickelt, dann in MacOS X umgesetzt und später sind sie ins iPhone gewandert. Aber auch außerhalb von Apple-Systemen wurden und werden sie nach und nach übernommen, sodass heutzutage eigentlich alle Systeme nach diesen Prinzipien funktionieren und diese logische Trennung vornehmen.
Sie wissen bereits, was es mit Objekten auf sich hat: Diese vereinigen Daten und Funktionen, die mit diesen Daten arbeiten. Sie ermöglichen außerdem, Daten an andere, abgeleitete Objekte zu »vererben« und Funktionen und damit Verhalten zu modifizieren. Damit ist es möglich, recht leistungsfähige Teile zu entwickeln.
So haben beispielsweise die drei grafischen UI-Elemente, die Sie bereits kennengelernt haben , der UIButton
, der UISlider
und das UILabel
die gleiche Basisklasse namens UIControl
, das ein Element der grafischen Benutzeroberfläche darstellt. UIControl
seinerseits hat als Basisklasse UIView
, das beliebige Elemente der grafischen Benutzeroberfläche repräsentiert.
Nun könnten Sie hergehen und ganze Programme mit nur einem einzigen Objekt schreiben. Oder Sie könnten das Streichholzspiel aus Kapitel 6 in ein Objekt packen und so erweitern, dass es sich automatisch am Bildschirm darstellen kann. Die Möglichkeiten sind schier unbegrenzt. Und genau darin besteht das Problem: Wenn Sie alles machen, was Sie können, treffen Sie möglicherweise eine ungeschickte Entscheidung und merken es nicht. Möglicherweise programmieren Sie Sachen doppelt, wenn Sie mit einem Kollegen zusammenarbeiten. Vielleicht funktioniert alles korrekt, aber Ihr Programm ist für andere Programmierer völlig unverständlich. Oder Sie wollen Ihr Programm erweitern und auch einfache Änderungen sind extrem umständlich und kompliziert.
Alle diese Probleme lassen sich durch geschickte Absprachen vermindern – wahrscheinlich nicht völlig vermeiden, aber auf jeden Fall bessern. Eine solche Absprache besteht darin, Objekte möglichst wiederverwendbar zu machen. Beispielsweise soll ein Button nicht nur in Ihrem Programm, sondern auch in anderen Programmen verwendbar sein. Wenn Sie ein eigenes UI-Element, zum Beispiel eine Telefon-Wählscheibe, programmieren, so sollte diese in möglichst vielen anderen Programmen verwendbar sein. Dafür machen Sie möglichst wenig Annahmen über den Verwendungszweck und konzentrieren sich darauf, was die Wählscheibe tatsächlich für einen Programmierer tun muss: Sie muss nur eine Zahl zurückliefern. Wie diese Zahl dann später benutzt wird, darf für die Wählscheibe nicht relevant sein!
Genau dieses Prinzip ist auch beim UIButton
umgesetzt: Er kann auf das Antippen durch den Benutzer reagieren und der Text kann eingestellt werden. Zusätzlich kann er auf Verlangen noch deaktiviert werden. Er kann aber nicht entscheiden, was ein Programm mit dem Antippen auslöst. Diese Funktionalität entscheidet jeder Programmierer, der einen Button verwendet, immer wieder aufs Neue. Dadurch kommen Buttons in allen Programmen sehr häufig vor und können für sehr viele verschiedene Funktionen wiederverwendet werden, ohne dass Apple (das die Klasse UIButton
programmiert hat) diese Programme mitentwickeln muss.
Analog könnten Sie eine Klasse definieren, die UIView
als Basisklasse besitzt und eine Anzahl von Streichhölzern grafisch am Bildschirm darstellt. Dies wäre für die grafische Version des Streichholzspiels aus Kapitel 6 sehr wünschenswert. Natürlich macht es in diesem Fall ebenfalls Sinn, wenn diese Klasse eine Eigenschaft zahl
hätte, die einfach die Zahl der Hölzer angibt, die gezeichnet werden sollen. Diese könnte dann vielleicht im Streichholzspiel verwendet werden, andererseits könnte sie aber auch jemand »zweckentfremden« und beispielsweise einen kreativen Fortschrittsbalken damit schreiben! Vielleicht möchte er Dinge anstellen, an die Sie gar nicht gedacht haben!
Denken Sie nun an das Beispiel aus Abschnitt 7.1. Hier handelt es sich um die allgemeine Implementierung eines Streichholzhaufens in der Klasse HolzHaufen
. Wenn Sie diese Klasse in ein grafisches Element umwandeln würden, so könnte dieser HolzHaufen
seinerseits eine bestimmte Zahl von Hölzern entfernen und möglicherweise später auch über Züge und Einschränkungen derselben entscheiden. Das wäre aber sehr ungünstig für eine Klasse, die Streichhölzer grafisch darstellen können soll. Dort sind irgendwelche Spielregeln absolut störend. Ein Fortschrittsbalken, zu dem man keine Hölzer hinzufügen kann, geht gar nicht!
Offenbar sind dies zwei völlig getrennte Prinzipien: Eine Klasse, die eine beliebige Anzahl von Hölzern darstellen kann, und eine Klasse, die bestimmte Regeln und Einschränkungen im Streichholzspiel kennt. Sinnvollerweise bietet es sich an, dafür auch zwei verschiedene Klassen zu definieren. Die eine kümmert sich um die Geschäftslogik, das heißt die Regeln und Hölzerzahl im Spiel – die schon bekannte Klasse HolzHaufen
, und die andere kümmert sich nur um die Darstellung ich nenne sie jetzt einfach mal HolzAnzeige
. Die erste Klasse nennt man daher das Modell und die zweite Klasse die Präsentation, auf Englisch Model und View.
Wenn diese beiden Klassen aber so universell einsetzbar sein sollen, wie können Sie das praktisch erreichen? Die Klasse HolzHaufen
»weiß« ja nichts von HolzAnzeige
und umgekehrt. Die Antwort liegt in einer weiteren Klasse, die die beiden Klassen HolzHaufen
und HolzAnzeige
kennt und zwischen beiden vermitteln kann. Diese Klasse ist die sogenannte Steuerung, auf Englisch Controller. Sie haben bereits diese Klassen kennengelernt, denn alle ViewController
sind derartige Steuerungsklassen, die Anzeigen mit Informationen versorgen und auf Benutzereingaben reagieren.
Durch diese Dreiteilung erreichen Sie, dass
HolzHaufen
unabhängig von der Benutzeroberfläche funktioniert undHolzAnzeige
unabhängig davon funktioniert, was denn genau dargestellt werden soll.Die Abhängigkeit liegt einzig und allein in einer weiteren Klasse HolzspielViewController
, die die beiden Klassen zusammenbringt und dafür sorgen muss, dass die Anzeige immer die korrekte Zahl der Hölzer darstellt. Außerdem sollte die Steuerungsklasse die Benutzereingaben entgegennehmen, wenn der Benutzer eine bestimmte Zahl von Hölzern entfernen möchte.
Grafisch ist dieses Konzept in Abbildung 13.8 dargestellt. Die Besonderheit dieses Konzepts besteht darin, dass sich jede Klasse in Ihrer App eindeutig einer der drei Kategorien Modell, Präsentation oder Steuerung zuordnen lässt. Zu jeder Klasse in Ihrer App sollten Sie jederzeit genau sagen können, wohin diese gehört. Wenn Sie bei einer Klasse nicht sicher sind, so ist dies ein Zeichen dafür, dass Sie diese möglicherweise umschreiben und zwei einzelne Klassen daraus machen sollten.
Abbildung 13.8 Das Konzept von Modell-Präsentation-Steuerung. Jede Klasse einer App lässt sich eindeutig einer bestimmten Funktion zuordnen.
Natürlich werde ich Sie nicht zwingen, diese Unterteilung einzuhalten und möglicherweise werden Sie auch manchmal mehrere gleichwertig erscheinende Alternativen finden. Aber spätestens, wenn Ihre Programme komplexer und umfangreicher werden, wird sich der Nutzen dieser Unterteilung zeigen. Ihre Programme werden lesbarer und verständlicher und Sie werden sie einfacher und besser erweitern können. Im folgenden Kapitel werde ich die Umsetzung dieses Konzepts an einigen praktischen Beispielen zeigen.
fizzBuzz
in den
MasterViewController
zu kopieren und zu verwenden. War das geschickt? Wie könnten Sie es besser machen?