Kapitel 13 Tabellen und Programmfluss auf dem iPhone

Inhalt

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.

13.1 Tabellen auf dem iPhone

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:

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.

IMG

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.

13.2 Praxisbeispiel: FizzBuzz mit Tabellen

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.

13.2.1. Die Vorlage für Tabellen

Dafür gehen Sie folgendermaßen vor:

1. Um diese Vorlage zu benutzen, erstellen Sie wieder ein neues Projekt mit der Tastenkombination IMG + IMG + IMG.
2. Dann wählen Sie in der Sektion IOS die Gruppe APPLICATION aus und anschließend in der rechten Seite die MASTER-DETAIL APPLICATION (siehe Abbildung 13.2).
3. Klicken Sie dann auf NEXT und geben Sie als PRODUCT NAME den Namen »FizzBuzzTabelle« ein. Die anderen Einstellungen können Sie so belassen, wie es in Abbildung 13.3 gezeigt ist.
4. Klicken Sie dann wieder auf NEXT. Legen Sie den Speicherort fest und klicken Sie dann auf CREATE.

IMG

Abbildung 13.2 Die Auswahl der Projektvorlage für eine Tabelle, die »Master-Detail Application«

IMG

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 IMG + IMG sowie IMG + IMG 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.

IMG

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 IMG + IMG 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.

13.2.2. Das Einrichten der Datenquelle

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:

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:

1. Die Methode 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.
2. Die Methode 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.
3. Die Methode 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.
4. Zuletzt können Sie die Eigenschaft 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.


Tipp
Methoden wie 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.

13.2.3. Super: Aufrufen der Basisklasse

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.

13.2.4. Ganze Geschichten erzählen: Den zweiten Bildschirm verwenden

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.


Hintergrund
View Controller Ursprünglich war eine Klasse, die 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.
Die Tabelle eines 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.
Seit der Einführung des iPads ist diese Regelung in den letzten Jahren allerdings nach und nach aufgeweicht worden. Dort gab es weitere grafische Elemente wie ein Popover und einen Split View, die Sichten in einer Art Fensterkonzept darstellen können. Dennoch ist es grundsätzlich eine sinnvolle Idee, wenn Sie für jeden Screen eine einzelne Klasse zur Verfügung stellen.

IMG

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:

1. Ganz links ist es die Klasse UISplitViewController. Diese Klasse ist ausgegraut, das bedeutet, dass es eine Klasse von iOS ist.
2. In der Mitte gibt es zwei Bildschirme, die mit UINavigationController assoziiert sind.
3. Rechts oben ist der Bildschirm des MasterViewController.
4. Links unten ist der Bildschirm des 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.

IMG

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.

IMG

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:

1. Der neue Bildschirm mit seinen grafischen Elementen wird erzeugt und entsprechend eingerichtet.
2. Das assoziierte Objekt 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.
3. Die Methode prepareForSegue von MasterViewController wird aufgerufen. Hier können Sie eigenen Programmtext eingeben, der vor dem eigentlichen Übergang ausgeführt wird.
4. Wenn 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.
5. Der Übergang mit einer kleinen Animation (der Bildschirm wird von rechts nach links geschoben) findet statt.

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:

1. Zunächst einmal gibt es die Eigenschaft 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.

2. Es gibt eine weitere Eigenschaft 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.

3. Das eigentliche Anpassen des Labeltextes auf den Wert von detailItem passiert in der Methode configureView. Hier wird sichergestellt, dass der Inhalt nicht nil ist und dann anschließend der Text des Labels gesetzt.
4. Die Methode 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.

5. Die Methode 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.


Tipp
Versuchen Sie immer, die Implementierungsdetails einer Klasse vor dem Aufrufer zu verbergen. Bieten Sie allgemeine Eigenschaften an, die nicht davon abhängen, was mit einer Eigenschaft passiert.

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:

1. Der Übergang vom Typ UIStoryboardSegue wird als Parameter segue übergeben.
2. Der zweite Parameter ist das Objekt, das den Übergang ausgelöst hat, der sender. Dieses Objekt ist im Moment nicht relevant.
3. Die Eigenschaft 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.
4. Die Konstante 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.
5. Den 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.

6. Die Tabellenzeile selbst lautet 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.
7. Dies führt dann dazu, dass der zweite Bildschirm den richtigen Text im Label anzeigt.
8. Beachten Sie, dass die Zeilenzählung von 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 IMG + IMG starten und verschiedene Zeilen in der Tabelle auswählen.

13.2.5. Das Ergebnis: Wenig Arbeit für Sie

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:

1. Eine sehr große Tabelle, die flüssig scrollt und sich  wie es sich bei Apple gehört  sehr einfach und natürlich bedienen lässt
2. Einen animierten Übergang zwischen zwei Bildschirmen in beide Richtungen
3. Den Datenaustausch zwischen diesen beiden Bildschirmen

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.

13.3 MVC  Das Geheimrezept, mit dem Ihre Programme richtig elegant werden

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

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.


Tipp
Versuchen Sie, für jede Klasse eine eindeutige Zuordnung entweder als »Modell«, als »Präsentation« oder als »Steuerung« zu finden.
Wenn Sie eine Klasse in mehrere Kategorien einordnen könnten, so ist dies ein Zeichen, dass diese Klasse zu viel macht und lieber in eine oder mehrere Klassen aufgespalten werden sollte.

IMG

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.

13.4 Aufgaben

1. In Abschnitt 13.2.2 habe ich Ihnen empfohlen, die Funktion fizzBuzz in den MasterViewController zu kopieren und zu verwenden. War das geschickt? Wie könnten Sie es besser machen?
2. Vielleicht kommen Ihnen die Prinzipien von Datenquellen kompliziert vor. Der besondere Vorteil der Lösung ist jedoch, dass Ihr Programm auch für gigantische Tabellen funktioniert. Probieren Sie es einmal mit einer Tabelle mit 10000 Zeilen aus!