1. Code
  2. Mobile Development
  3. iOS Development

Grundlagen der Tabellenansicht

Tabellenansichten gehören zu den am häufigsten verwendeten Komponenten des UIKit-Frameworks und sind ein wesentlicher Bestandteil der Benutzererfahrung auf der iOS-Plattform. Tabellenansichten machen eine Sache und sie machen es sehr gut: Präsentieren Sie eine geordnete Liste von Elementen. Die UITableView-Klasse ist ein guter Ort, um unsere Reise durch das UIKit-Framework fortzusetzen, da sie mehrere Schlüsselkonzepte von Cocoa Touch und UIKit kombiniert, einschließlich Ansichten, Protokollen und Wiederverwendbarkeit.
Scroll to top
This post is part of a series called Learn iOS SDK Development From Scratch.
First Steps with UIKit
Navigation Controllers and View Controller Hierarchies

German (Deutsch) translation by Katharina Grigorovich-Nevolina (you can also view the original English article)

Tabellenansichten gehören zu den am häufigsten verwendeten Komponenten des UIKit-Frameworks und sind ein wesentlicher Bestandteil der Benutzererfahrung auf der iOS-Plattform. Tabellenansichten machen eine Sache und sie machen es sehr gut: Präsentieren Sie eine geordnete Liste von Elementen. Die UITableView-Klasse ist ein guter Ort, um unsere Reise durch das UIKit-Framework fortzusetzen, da sie mehrere Schlüsselkonzepte von Cocoa Touch und UIKit kombiniert, einschließlich Ansichten, Protokollen und Wiederverwendbarkeit.


Einführung

Die UITableView-Klasse, eine der Schlüsselkomponenten des UIKit-Frameworks, ist in hohem Maße für die Anzeige einer geordneten Liste kleiner und großer Elemente optimiert. Tabellenansichten können angepasst und an eine Vielzahl von Anwendungsfällen angepasst werden. Die Grundidee bleibt jedoch unverändert und enthält eine geordnete Liste von Elementen.

Die UITableView-Klasse ist nur für die Darstellung von Daten als Zeilenliste verantwortlich. Die angezeigten Daten werden vom dataSource-Objekt der Tabellenansicht verwaltet. Das dataSource-Objekt kann ein beliebiges Objekt sein, das dem UITableViewDataSource-Protokoll entspricht. Das Datenquellenobjekt der Tabellenansicht ist häufig der View Controller, der die Ansicht verwaltet. Die Tabellenansicht ist eine Unteransicht, wie wir später in diesem Artikel sehen werden.

Ebenso ist die Tabellenansicht nur für die Erkennung von Berührungen in der Tabellenansicht verantwortlich. Es ist nicht dafür verantwortlich, auf Berührungen zu reagieren. Zusätzlich zum dataSource-Objekt der Tabellenansicht verfügt die Tabellenansicht über ein delegate-Objekt. Immer wenn die Tabellenansicht ein Berührungsereignis erkennt, benachrichtigt sie das delegate-Objekt über das Berührungsereignis. Es liegt in der Verantwortung des delegate-Objekts der Tabellenansicht, auf das Berührungsereignis zu reagieren.

Durch ein Datenquellenobjekt, das seine Daten verwaltet, und ein delegiertes Objekt, das die Benutzerinteraktion verwaltet, kann sich die Tabellenansicht auf die Datenpräsentation konzentrieren. Das Ergebnis ist eine hochgradig wiederverwendbare und leistungsfähige UIKit-Komponente, die perfekt mit dem im vorherigen Artikel beschriebenen MVC-Muster(Model-View-Controller) übereinstimmt.

Die UITableView-Klasse erbt von UIView und ist daher nur für die Anzeige von Anwendungsdaten verantwortlich.

Ein Datenquellenobjekt ist ähnlich, aber nicht identisch mit einem Delegatenobjekt. Ein delegiertes Objekt ist die delegierte Steuerung der Benutzeroberfläche durch das delegierende Objekt. Ein Datenquellenobjekt ist jedoch die delegierte Kontrolle über Daten.

Die Tabellenansicht fragt das Datenquellenobjekt nach den Daten, die angezeigt werden sollen. Dies bedeutet, dass das Datenquellenobjekt auch für die Verwaltung der Daten verantwortlich ist, die es der Tabellenansicht zuführt.


Komponenten der Tabellenansicht

Die UITableView-Klasse erbt von UIScrollView, einer UIView-Unterklasse, die die Anzeige von Inhalten unterstützt, die größer als das Fenster der Anwendung sind.

Eine UITableView-Instanz besteht aus Zeilen, wobei jede Zeile eine Zelle, eine Instanz von UITableViewCell oder eine Unterklasse davon enthält. Im Gegensatz zu UITableViews Gegenstück unter OS X, NSTableView, sind Instanzen von UITableView eine Spalte breit. Verschachtelte Datensätze und Hierarchien können mithilfe einer Kombination aus Tabellenansichten und Navigationscontrollern (UINavigationController) angezeigt werden, wie im nächsten Artikel dieser Reihe gezeigt wird.

Ich habe bereits erwähnt, dass Tabellenansichten nur für die Anzeige von Daten zuständig sind, die vom Datenquellenobjekt geliefert werden, und für die Erkennung von Berührungsereignissen, die an das delegierte Objekt weitergeleitet werden. Eine Tabellenansicht ist nichts anderes als eine Ansicht, die eine Reihe von Unteransichten verwaltet, genauer gesagt die Zellen der Tabellenansicht.

Ein neues Projekt

Anstatt Sie mit Theorie zu überladen, ist es besser, ein neues iOS-Projekt zu erstellen und Ihnen zu zeigen, wie Sie eine Tabellenansicht einrichten, mit Daten füllen und auf Berührungsereignisse reagieren lassen.

Öffnen Sie Xcode, erstellen Sie ein neues Projekt (Datei > Neu > Projekt ...) und wählen Sie die Single View-Anwendung-Vorlage aus.

Benennen Sie die Projekt-Tabellenansichten, weisen Sie einen Organisationsnamen und eine Firmenkennung zu und stellen Sie Geräte auf iPhone ein. Teilen Sie Xcode mit, wo Sie das Projekt speichern möchten, und klicken Sie auf Erstellen.

Das neue Projekt sollte vertraut aussehen, da wir im vorherigen Artikel dieselbe Projektvorlage ausgewählt haben. Xcode hat bereits eine Anwendungsdelegatenklasse für uns erstellt (TSPAppDelegate) und uns zunächst eine View-Controller-Klasse (TSPViewController) gegeben.


Hinzufügen einer Tabellenansicht

Erstellen Sie das Projekt und führen Sie es aus, um zu sehen, womit wir beginnen. Der weiße Bildschirm, den Sie beim Ausführen der Anwendung im iOS-Simulator sehen, ist die Ansicht des View Controllers (TSPViewController), den Xcode im Storyboard für uns instanziiert hat. Schauen Sie sich den vorherigen Artikel an, wenn Sie Ihr Gedächtnis auffrischen müssen.

Der einfachste Weg, der Ansicht des View Controllers eine Tabellenansicht hinzuzufügen, ist das Haupt-Storyboard des Projekts. Öffnen Sie das Storyboard Main.storyboard und suchen Sie die Objektbibliothek rechts, wie im vorherigen Artikel beschrieben. Durchsuchen Sie die Objektbibliothek und ziehen Sie eine UITableView-Instanz in die Ansicht des View Controllers.

Die Abmessungen der Tabellenansicht sollten automatisch an die Grenzen der Ansicht des View Controllers angepasst werden. Sie können die Abmessungen der Tabellenansicht manuell anpassen, indem Sie die weißen Quadrate an den Rändern der Tabellenansicht ziehen. Denken Sie daran, dass die weißen Quadrate nur sichtbar sind, wenn die Tabellenansicht ausgewählt ist.

Dies ist so ziemlich alles, was wir tun müssen, um der Ansicht unseres View Controllers eine Tabellenansicht hinzuzufügen. Erstellen Sie das Projekt und führen Sie es aus, um das Ergebnis im iOS-Simulator anzuzeigen. Sie sollten eine Tabellenansicht ohne Daten sehen.

Eine Tabellenansicht verfügt über zwei Standardstile: "Einfach" und "Gruppiert". Um den aktuellen Stil der Tabellenansicht (Plain) zu ändern, wählen Sie die Tabellenansicht im Storyboard aus, öffnen Sie den Attributinspektor und ändern Sie das Stilattribut in Gruppiert. Für dieses Projekt arbeiten wir jedoch mit einer plain-Tabellenansicht. Stellen Sie daher sicher, dass Sie den Stil der Tabellenansicht wieder auf "Einfach" ändern.


Verbinden von Datenquelle und Delegat

Sie wissen bereits, dass eine Tabellenansicht eine Datenquelle und ein Delegatenobjekt haben soll. Unsere Tabellenansicht verfügt jedoch noch nicht über eine Datenquelle oder einen Delegaten. Wir müssen die dataSource- und delegate-Outlets der Tabellenansicht mit einem Objekt verbinden, das den Protokollen UITableViewDataSource und UITableViewDelegate entspricht.

In den meisten Fällen ist dieses Objekt des View Controlles, der die Ansicht verwaltet, deren Unteransicht die View ControllerTabellenansicht ist. Wählen Sie die Tabellenansicht im Storyboard aus, öffnen Sie den Verbindungsinspektor rechts und ziehen Sie wie unten gezeigt aus dem dataSource-Ausgang (dem leeren Kreis rechts) auf den View Controller. Machen Sie dasselbe für die delegate-Outlet. Unser View Controller ist jetzt als Datenquelle und Delegat der Tabellenansicht festgelegt.

Wenn Sie das Projekt jetzt erstellen und ausführen würden, würde es fast sofort abstürzen. Der Grund dafür wird in wenigen Augenblicken klar. Bevor wir uns das UITableViewDataSource-Protokoll genauer ansehen, müssen wir die Header-Datei der View-Controller-Klasse TSPViewController aktualisieren.

Die Datenquellen- und Delegate-Objekte der Tabellensicht müssen dem UITableViewDataSource- und UITableViewDelegate-Protokoll bzw.entsprechen. Wie wir bereits früher in dieser Serie gesehen haben, werden Protokolle zwischen spitzen Klammern nach der Oberklasse in der Header-Datei angegeben. Mehrere Protokolle werden durch Kommas getrennt.

1
#import <UIKit/UIKit.h>

2
3
@interface TSPViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
4
5
@end

Erstellen der Datenquelle

Bevor wir mit der Implementierung der Methoden des Datenquellenprotokolls beginnen, müssen einige Daten in der Tabellenansicht angezeigt werden. Die Daten werden in einer Instanz von NSArray gespeichert, daher müssen wir zuerst unserer View Controller-Klasse eine neue Eigenschaft hinzufügen.

Wählen Sie die Header-Datei des View Controllers, TSPViewController.h, aus und fügen Sie eine Eigenschaft mit dem Namen fruits hinzu. Die Eigenschaft sollte vom Typ NSArray sein.

1
#import <UIKit/UIKit.h>

2
3
@interface TSPViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
4
5
@property NSArray *fruits;
6
7
@end

In der viewDidLoad-Methode des View Controllers füllen wir die fruits-Eigenschaft mit einer Liste von Fruchtnamen, die wir etwas später in der Tabellenansicht anzeigen werden. Die viewDidLoad-Methode wird automatisch aufgerufen, nachdem die Ansicht des View Controllers und seine Unteransichten in den Speicher geladen wurden, daher der Name. Es ist daher ein guter Ort, um das fruits-Array zu initialisieren.

1
- (void)viewDidLoad {
2
    [super viewDidLoad];
3
    
4
    self.fruits = @[@"Apple", @"Pineapple", @"Orange", @"Banana", @"Pear", @"Kiwi", @"Strawberry", @"Mango", @"Walnut", @"Apricot", @"Tomato", @"Almond", @"Date", @"Melon", @"Water Melon", @"Lemon", @"Blackberry", @"Coconut", @"Fig", @"Passionfruit", @"Star Fruit"];
5
}

In viewDidLoad weisen wir der fruits-Eigenschaft des View-Controllers ein Array-Literal zu. Dies sollte Ihnen bekannt vorkommen, wenn Sie den vorherigen Artikel gelesen haben. Das fruits-Array enthält die Daten, die in der Tabellenansicht angezeigt werden.


Die UIViewController-Klasse, die Oberklasse der TSPViewController-Klasse, definiert auch eine viewDidLoad-Methode. Die TSPViewController-Klasse überschreibt die von der UIViewController-Klasse definierte viewDidLoad-Methode.

Das Überschreiben einer Methode einer Oberklasse ist niemals ohne Risiko. Was ist, wenn die UIViewController-Klasse einige wichtige Dinge in der viewDidLoad-Methode ausführt? Wie stellen wir sicher, dass nichts kaputt geht, wenn wir die viewDidLoad-Methode überschreiben?

In solchen Situationen ist es wichtig, zuerst die viewDidLoad-Methode der Oberklasse aufzurufen, bevor Sie etwas anderes in der viewDidLoad-Methode tun. Das Schlüsselwort super bezieht sich auf die Oberklasse und wir senden ihr eine Nachricht von viewDidLoad, die die viewDidLoad-Methode der Oberklasse aufruft. Dies ist ein wichtiges Konzept, das Sie verstehen sollten. Stellen Sie daher sicher, dass Sie dies richtig verstehen, bevor Sie fortfahren.


Datenquellenprotokoll

Da wir den View Controller als Datenquellenobjekt der Tabellenansicht zugewiesen haben, fragt die Tabellenansicht den View Controller, was er anzeigen soll. Die erste Information, die die Tabellenansicht von ihrem dataSource-Objekt wünscht, ist die Anzahl der Abschnitte, die angezeigt werden sollen. Die Tabellenansicht ruft dazu die Methode numberOfSectionsInTableView: für ihr dataSource-Objekt auf. Dies ist eine optionale Methode des UITableViewDataSource-Protokolls. Wenn das dataSource-Objekt der Tabellenansicht diese Methode nicht implementiert, wird in der Tabellenansicht davon ausgegangen, dass nur ein Abschnitt angezeigt werden muss. Wir implementieren diese Methode trotzdem, da wir sie später in diesem Artikel benötigen werden.

Sie fragen sich vielleicht: "Was ist ein Abschnitt mit Tabellenansicht?" Ein Tabellenansichtsabschnitt ist eine Gruppe von Zeilen. Die Anwendung "Kontakte" unter iOS gruppiert Kontakte beispielsweise anhand des ersten Buchstabens des Vor- oder Nachnamens. Jede Gruppe von Kontakten bildet einen Abschnitt, dem eine Abschnittsüberschrift am oberen Rand des Abschnitts und/oder eine Abschnittsfußzeile am unteren Rand des Abschnitts vorangestellt sind.

Die Methode numberOfSectionsInTableView: akzeptiert ein Argument, tableView. Dies ist die Tabellenansicht, die die Nachricht an das Datenquellenobjekt gesendet hat. Dies ist wichtig, da das Datenquellenobjekt bei Bedarf die Datenquelle mehrerer Tabellenansichten sein kann. Wie Sie sehen können, ist die Implementierung von numberOfSectionsInTableView: recht einfach.

1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
2
    return 1;
3
}

Sie werden möglicherweise durch die Verwendung von NSInteger als Rückgabetyp von numberOfSectionsInTableView: abgeworfen. NSInteger ist ein Datentyp, der im Foundation Framework definiert ist. NSInteger und seine vorzeichenlose Variante NSUInteger haben eine dynamische Definition, um die Portabilität zu verbessern.

Nachdem die Tabellenansicht weiß, wie viele Abschnitte angezeigt werden müssen, fragt sie das dataSource-Objekt, wie viele Zeilen jeder Abschnitt enthält. Für jeden Abschnitt in der Tabellenansicht sendet die Tabellenansicht dem dataSource-Objekt eine Nachricht von tableView:numberOfRowsInSection:. Die Methode akzeptiert zwei Argumente, die Tabellenansicht, die die Nachricht sendet, und den Abschnitt, dessen Anzahl die Zeilen wissen soll.

Die Implementierung dieser Methode ist ziemlich einfach, wie Sie unten sehen können. Wir deklarieren zunächst eine Ganzzahl mit dem Namen numberOfRows und weisen ihr die Anzahl der Elemente im fruits-Array zu, indem wir count im Array aufrufen. Wir geben numberOfRows zurück, wie es die Methode erwartet.

1
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
2
    NSInteger numberOfRows = [self.fruits count];
3
    return numberOfRows;
4
}

Die Implementierung dieser Methode ist so einfach, dass wir sie genauso gut etwas präziser gestalten können. Sehen Sie sich die Implementierung unten an, um sicherzustellen, dass Sie verstehen, was sich geändert hat.

1
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
2
    return [self.fruits count];
3
}

Wenn Sie das Projekt jetzt erstellen und ausführen würden, würde die Anwendung immer noch abstürzen. Die Tabellenansicht erwartet, dass die Datenquelle, unser View Controller, für jede Zeile jedes Abschnitts eine Tabellenansichtszelle zurückgibt. Die Meldung, die nach dem Absturz in der Konsole angezeigt wird, zeigt deutlich, was als Nächstes zu tun ist.

1
UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:

Um dies zu beheben, müssen wir tableView:cellForRowAtIndexPath: implementieren, eine weitere Methode des UITableViewDataSource-Protokolls. Wie die meisten Objective-C-Methodennamen ist der Name der Methode sehr aussagekräftig. Durch Senden dieser Nachricht an das dataSource-Objekt der Tabellenansicht fragt die Tabellenansicht ihre Datenquelle nach der Tabellenansichtszelle der von indexPath angegebenen Zeile.

Bevor ich fortfahre, möchte ich mir eine Minute Zeit nehmen, um über die NSIndexPath-Klasse zu sprechen. In der Dokumentation wird erläutert: "Die NSIndexPath-Klasse repräsentiert den Pfad zu einem bestimmten Knoten in einem Baum verschachtelter Array-Sammlungen." Eine Instanz dieser Klasse kann einen oder mehrere Indizes enthalten. In unserer Tabellenansicht enthält sie einen Index für den Abschnitt, in dem sich ein Element befindet, und die Zeile dieses Elements im Abschnitt.

Eine Tabellenansicht ist niemals tiefer als zwei Ebenen, wobei die erste Ebene der Abschnitt und die zweite Ebene die Zeile im Abschnitt ist. Obwohl NSIndexPath eine Foundation-Klasse ist, fügt das UIKit-Framework der Klasse eine Handvoll zusätzlicher Methoden hinzu, die das Arbeiten mit Tabellenansichten erleichtern. Lassen Sie uns die Implementierung der tableView:cellForRowAtIndexPath:-Methode untersuchen.

1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
2
    static NSString *CellIdentifier = @"Cell Identifier";
3
    
4
    [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier];
5
    
6
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
7
    
8
    // Fetch Fruit

9
    NSString *fruit = [self.fruits objectAtIndex:[indexPath row]];
10
    
11
    [cell.textLabel setText:fruit];
12
    
13
    return cell;
14
}

In der ersten Zeile von tableView:cellForRowAtIndexPath: deklarieren wir eine statische Zeichenfolge. Das Deklarieren der Zeichenfolge als statisch hat den Vorteil, dass der Compiler nur eine Kopie dieser Zeichenfolge verwendet, anstatt bei jedem Aufruf der tableView:cellForRowAtIndexPath:-Methode eine neue Zeichenfolge zu erstellen. Dies hilft, die Speichernutzung der Tabellenansicht gering zu halten.

1
static NSString *CellIdentifier = @"Cell Identifier";

Wiederverwenden von Tabellenansichtszellen

Im vorherigen Artikel habe ich Ihnen gesagt, dass Ansichten eine wichtige Komponente einer iOS-Anwendung sind. Ansichten sind jedoch in Bezug auf den Speicher und die Verarbeitungsleistung, die sie verbrauchen, teuer. Bei der Arbeit mit Tabellenansichten ist es daher wichtig, Tabellenansichtszellen so weit wie möglich wiederzuverwenden. Durch die Wiederverwendung von Tabellenansichtszellen muss die Tabellenansicht nicht jedes Mal eine neue Tabellenansichtszelle von Grund auf neu initialisieren, wenn eine neue Zeile auf den Bildschirm gezeichnet werden muss.

Zellen der Tabellenansicht, die sich außerhalb des Bildschirms bewegen, werden nicht in den Papierkorb geworfen. Tabellenansichtszellen können zur Wiederverwendung markiert werden, indem während der Initialisierung eine Wiederverwendungskennung angegeben wird. Wenn eine zur Wiederverwendung markierte Tabellenansichtszelle vom Bildschirm entfernt wird, wird sie in der Tabellenansicht zur späteren Wiederverwendung in eine Wiederverwendungswarteschlange gestellt.

Wenn das dataSource-Objekt der Tabellenansicht die Tabellenansicht nach einer neuen Zelle fragt und eine Wiederverwendungskennung angibt, überprüft die Tabellenansicht zunächst die Wiederverwendungswarteschlange, um zu überprüfen, ob eine Tabellenansichtszelle mit der angegebenen Wiederverwendungskennung verfügbar ist. Wenn keine Tabellenansichtszelle verfügbar ist, instanziiert die Tabellenansicht eine neue und übergibt sie an das dataSource-Objekt. Das passiert in der nächsten Codezeile.

1
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

Das dataSource-Objekt der Tabellenansicht fragt die Tabellenansicht nach einer Tabellenansichtszelle, indem eine Nachricht von dequeueReusableCellWithIdentifier:forIndexPath: gesendet wird. Die Methode akzeptiert den zuvor erwähnten Wiederverwendungsbezeichner sowie den Indexpfad der Tabellenansichtszelle.

Woher weiß die Tabellenansicht, wie eine neue Tabellenansichtszelle erstellt wird? Mit anderen Worten, woher weiß die Tabellenansicht, welche Klasse zum Instanziieren einer neuen Tabellenansichtszelle verwendet werden soll? Die Antwort ist einfach. Vor dem Senden der Tabellenansicht muss die Nachricht dequeueReusableCellWithIdentifier:forIndexPath:, die Tabellenansicht wissen, welche Klasse mit einer bestimmten Wiederverwendungskennung verwendet werden soll. Dazu wird der Tabellenansicht eine Nachricht von registerClass:forCellReuseIdentifier: gesendet und die zu verwendende Klasse und angegeben eine Wiederverwendungskennung. Schauen Sie sich das folgende Beispiel an.

1
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier];
2
3
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

Konfigurieren der Tabellenansichtszelle

Der nächste Schritt besteht darin, die Tabellenansichtszelle mit den im fruits-Array gespeicherten Daten zu füllen. Dies bedeutet, dass wir wissen müssen, welches Element aus dem fruits-Array verwendet werden soll, was wiederum bedeutet, dass wir den Zeilenindex der Tabellenansichtszelle irgendwie kennen müssen.

Das indexPath-Argument der tableView:cellForRowAtIndexPath:-Methode enthält diese Informationen. Wie bereits erwähnt, gibt es einige zusätzliche Methoden, um das Arbeiten mit Tabellenansichten zu vereinfachen. Eine dieser Methoden ist row, die die Zeile für die Zelle zurückgibt. Wir holen die richtige fruits, indem wir dem fruits-Array eine Nachricht von objectAtIndex: senden und die richtige Zeile übergeben, wie unten gezeigt.

1
// Fetch Fruit

2
NSString *fruit = [self.fruits objectAtIndex:[indexPath row]];

Schließlich setzen wir den Text der textLabel-Eigenschaft der Tabellenansichtszelle auf den fruits-Namen, den wir aus dem fruits-Array abgerufen haben. Die UITableViewCell-Klasse ist eine UIView-Unterklasse und verfügt über eine Reihe von Unteransichten. Eine dieser Unteransichten ist eine Instanz von UILabel. Mit dieser Bezeichnung wird der Name der Frucht in der Zelle der Tabellenansicht angezeigt.

1
[cell.textLabel setText:fruit];

Die tableView:cellForRowAtIndexPath:-Methode erwartet, dass wir eine Instanz der UITableViewCell-Klasse zurückgeben, und das tun wir am Ende der Methode.

1
return cell;

Erstellen Sie das Projekt und führen Sie es erneut aus. Sie sollten jetzt eine voll funktionsfähige Tabellenansicht haben, die mit dem Array von Fruchtnamen gefüllt ist, die in der fruits-Eigenschaft des View Controllers gespeichert sind.

Ein besserer Weg

Es gibt zwei Dinge, die mir an der Implementierung von  tableView:cellForRowAtIndexPath: nicht gefallen. Wir deklarieren zunächst eine statische Zeichenfolge, die wir als Kennung für die Wiederverwendung von Zellen verwenden. Es ist keine gute Praxis, dies in tableView:cellForRowAtIndexPath: zu tun. Verschieben Sie dieses Snippet an den Anfang des @implementation-Blocks in TSPViewController.h. Das ist ein viel besserer Ort. Es ist nicht erforderlich, diese Codezeile jedes Mal auszuführen, wenn die Tabellenansicht nach einer Zelle fragt.

1
@implementation TSPViewController
2
3
static NSString *CellIdentifier = @"Cell Identifier";

Wir sollten registerClass:forCellReuseIdentifier: in tableView:cellForRowAtIndexPath: nicht aufrufen. Diese Methode sollte nur einmal für jede Kennung zur Wiederverwendung von Zellen aufgerufen werden. Dies bedeutet, dass sie nicht in tableView:cellForRowAtIndexPath: gehört. Verschieben Sie diese Codezeile in die viewDidLoad-Methode des View Controllers.

Es gibt ein Problem. Wir haben keinen Zugriff auf die Tabellenansicht des View Controllers in viewDidLoad. Dies ist leicht zu beheben. Besuchen Sie TSPViewController.h, erstellen Sie einen Ausgang für die Tabellenansicht, die wir dem Storyboard hinzugefügt haben, und verbinden Sie den Ausgang im Storyboard mit der Tabellenansicht.

1
#import <UIKit/UIKit.h>

2
3
@interface TSPViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
4
5
@property IBOutlet UITableView *tableView;
6
7
@property NSArray *fruits;
8
9
@end

Auf diese Weise können wir die Tabellenansicht in der viewDidLoad-Methode als self.tableView referenzieren. Die aktualisierte Implementierung von viewDidLoad wird unten gezeigt.

1
- (void)viewDidLoad {
2
    [super viewDidLoad];
3
    
4
    self.fruits = @[@"Apple", ..., @"Star Fruit"];
5
    
6
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier];
7
}

Abschnitte

Bevor wir uns das UITableViewDelegate-Protokoll ansehen, möchte ich die aktuelle Implementierung des Datenquellenprotokolls ändern, indem ich der Tabellenansicht Abschnitte hinzufüge. Wenn die Liste der Früchte im Laufe der Zeit wachsen würde, wäre es besser und benutzerfreundlicher, die Früchte alphabetisch zu sortieren und sie basierend auf dem Anfangsbuchstaben der Früchte in Abschnitte zu gruppieren.

Wenn wir der Tabellenansicht Abschnitte hinzufügen möchten, reicht das aktuelle Array von Fruchtnamen nicht aus. Stattdessen müssen die Daten in Abschnitte unterteilt und die Früchte in jedem Abschnitt alphabetisch sortiert werden. Ein Wörterbuch (NSDictionary) ist der ideale Kandidat für diesen Zweck. Fügen Sie der Header-Datei des View-Controllers eine neue Eigenschaft mit dem Namen alphabetizedFruits hinzu und kehren Sie zur viewDidLoad-Methode in der Implementierungsdatei des View-Controllers zurück.

1
#import <UIKit/UIKit.h>

2
3
@interface TSPViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
4
5
@property IBOutlet UITableView *tableView;
6
7
@property NSArray *fruits;
8
@property NSDictionary *alphabetizedFruits;
9
10
@end

In viewDidLoad verwenden wir das fruits-Array, um ein Wörterbuch mit Früchten zu erstellen. Das Wörterbuch sollte für jeden Buchstaben des Alphabets eine Reihe von Früchten enthalten.

1
- (void)viewDidLoad {
2
    [super viewDidLoad];
3
    
4
    self.fruits = @[@"Apple", @"Pineapple", @"Orange", @"Banana", @"Pear", @"Kiwi", @"Strawberry", @"Mango", @"Walnut", @"Apricot", @"Tomato", @"Almond", @"Date", @"Melon", @"Water Melon", @"Lemon", @"Blackberry", @"Coconut", @"Fig", @"Passionfruit", @"Star Fruit"];
5
    
6
    self.alphabetizedFruits = [self alphabetizeFruits:self.fruits];
7
    
8
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier];
9
}

Das Wörterbuch wird in der Hilfsmethode alphabetizeFruits: erstellt, die das fruits-Array als Argument akzeptiert. Die alphabetizeFruits:-Methode mag auf den ersten Blick etwas überwältigend sein, aber ihre Implementierung ist eigentlich ziemlich einfach.

1
- (NSDictionary *)alphabetizeFruits:(NSArray *)fruits {
2
    NSMutableDictionary *buffer = [[NSMutableDictionary alloc] init];
3
    
4
    // Put Fruits in Sections

5
    for (int i = 0; i < [fruits count]; i++) {
6
        NSString *fruit = [fruits objectAtIndex:i];
7
        NSString *firstLetter = [[fruit substringToIndex:1] uppercaseString];
8
        
9
        if ([buffer objectForKey:firstLetter]) {
10
            [(NSMutableArray *)[buffer objectForKey:firstLetter] addObject:fruit];
11
            
12
        } else {
13
            NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:fruit, nil];
14
            [buffer setObject:mutableArray forKey:firstLetter];
15
        }
16
    }
17
    
18
    // Sort Fruits

19
    NSArray *keys = [buffer allKeys];
20
    for (int j = 0; j < [keys count]; j++) {
21
        NSString *key = [keys objectAtIndex:j];
22
        [(NSMutableArray *)[buffer objectForKey:key] sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
23
    }
24
    
25
    NSDictionary *result = [NSDictionary dictionaryWithDictionary:buffer];
26
    return result;
27
}

Wir erstellen zuerst ein veränderliches Wörterbuch (NSMutableDictionary), in dem die Abschnitte vorübergehend gespeichert werden, und durchlaufen dann die Früchte im fruits-Array, greifen nach dem ersten Buchstaben jeder Frucht und fügen ihn basierend auf dem ersten Buchstaben dem entsprechenden Array in hinzu das temporäre veränderbare Array. Jeder Abschnitt wird durch ein veränderliches Array (NSMutableArray) dargestellt.

In einer zweiten for-Schleife durchlaufen wir das temporäre Wörterbuch und sortieren jedes Obstarray alphabetisch. Wir erstellen dann ein neues Wörterbuch aus dem temporären veränderlichen Wörterbuch und geben dieses Wörterbuch am Ende der Methode zurück. Machen Sie sich keine Sorgen, wenn die Implementierung von alphabetizeFruits: nicht ganz klar ist. Der Schwerpunkt dieses Artikels liegt auf Tabellenansichten und nicht auf der Erstellung einer alphabetischen Liste von Früchten.

Anzahl der Abschnitte

Nachdem die neue Datenquelle eingerichtet wurde, müssen wir zunächst die numberOfSectionsInTableView:-Methode des UITableViewDataSource-Protokolls aktualisieren.

Die aktualisierte Implementierung ist recht einfach, wie Sie unten sehen können. Wir beginnen damit, das Wörterbuch alphabetizedFruits nach all seinen Schlüsseln zu fragen, indem wir ihm eine Nachricht von allKeys senden. Dadurch werden alle Schlüssel des Wörterbuchs herausgezogen und in ein Array eingefügt. Die Anzahl der Schlüssel im zurückgegebenen Array entspricht der Anzahl der Abschnitte in der Tabellenansicht.

1
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
2
    NSArray *keys = [self.alphabetizedFruits allKeys];
3
    return [keys count];
4
}

Als nächstes müssen wir tableView:numberOfRowsInSection: aktualisieren. Wie in numberOfSectionsInTableView: fragen wir zunächst alphabetizedFruits nach seinen Schlüsseln und sortieren dann das Array der Schlüssel. Das Sortieren des Schlüsselarrays ist wichtig, da die Schlüssel-Wert-Paare eines Wörterbuchs nicht geordnet sind. Dies ist ein wesentlicher Unterschied zu Arrays und führt häufig zu Entwicklern, die mit Objective-C noch nicht vertraut sind.

1
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
2
    NSArray *unsortedKeys = [self.alphabetizedFruits allKeys];
3
    NSArray *sortedKeys = [unsortedKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
4
    NSString *key = [sortedKeys objectAtIndex:section];
5
    NSArray *fruitsForSection = [self.alphabetizedFruits objectForKey:key];
6
    return [fruitsForSection count];
7
}

Im nächsten Schritt holen wir den richtigen Schlüssel für den Abschnitt und können dann alphabetizedFruits nach dem Array fragen, das mit diesem Schlüssel verknüpft ist. Wir geben dann die Anzahl der Elemente in dem Array zurück.

Die Änderungen, die wir an tableView:cellForRowAtIndexPath: vornehmen müssen, sind sehr ähnlich. Alles bleibt gleich, bis auf die Art und Weise, wie wir den Fruchtnamen abrufen, der in der Tabellenansichtszelle angezeigt wird.

1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
2
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
3
    
4
    // Fetch Fruit

5
    NSArray *unsortedKeys = [self.alphabetizedFruits allKeys];
6
    NSArray *sortedKeys = [unsortedKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
7
    NSString *key = [sortedKeys objectAtIndex:[indexPath section]];
8
    NSArray *fruitsForSection = [self.alphabetizedFruits objectForKey:key];
9
    NSString *fruit = [fruitsForSection objectAtIndex:[indexPath row]];
10
    
11
    [cell.textLabel setText:fruit];
12
    
13
    return cell;
14
}

Wenn Sie das Projekt erstellen und ausführen würden, würden Sie keine Abschnittsüberschriften wie in der Anwendung "Kontakte" sehen. Dies liegt daran, dass wir der Tabellenansicht mitteilen müssen, was in den einzelnen Abschnittsüberschriften angezeigt werden soll.

Die naheliegendste Möglichkeit besteht darin, den Namen jedes Abschnitts anzuzeigen, dh einen Buchstaben des Alphabets. Der einfachste Weg, dies zu tun, ist die Implementierung von tableView:titleForHeaderInSection:, einer anderen Methode, die im UITableViewDataSource-Protokoll definiert ist. Schauen Sie sich die Implementierung unten an. Es ähnelt der Implementierung von tableView:numberOfRowsInSection:.

1
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
2
    NSArray *keys = [[self.alphabetizedFruits allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
3
    NSString *key = [keys objectAtIndex:section];
4
    return key;
5
}

Erstellen Sie Ihre Anwendung, führen Sie sie aus und sehen Sie, wie die Tabellenansicht jetzt eine Liste von Abschnitten ist, wobei jeder Abschnitt eine alphabetische Liste von Früchten enthält.


Delegation

Zusätzlich zum UITableViewDataSource-Protokoll definiert das UIKit-Framework auch das UITableViewDelegate-Protokoll, dem das delegate-Objekt der Tabellenansicht entsprechen muss.

Im Storyboard haben wir unseren View Controller bereits als Delegaten der Tabellenansicht festgelegt. Obwohl wir keine der im UITableViewDelegate-Protokoll definierten Delegatenmethoden implementiert haben, funktioniert unsere Anwendung einwandfrei. Dies liegt daran, dass jede Methode des UITableViewDelegate-Protokolls optional ist.

Es wäre jedoch schön, auf Berührungsereignisse reagieren zu können. Immer wenn ein Benutzer eine Zeile berührt, sollten wir in der Lage sein, den Namen der entsprechenden Frucht in der Xcode-Konsole zu protokollieren. Obwohl dies nicht sehr nützlich ist, zeigt es Ihnen, wie das Delegatenmuster funktioniert.

Die Implementierung dieses Verhaltens ist einfach. Wir müssen lediglich die tableView:didSelectRowAtIndexPath:-Methode des UITableViewDelegate-Protokolls implementieren.

1
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
2
    // Fetch Fruit

3
    NSArray *unsortedKeys = [self.alphabetizedFruits allKeys];
4
    NSArray *sortedKeys = [unsortedKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
5
    NSString *key = [sortedKeys objectAtIndex:[indexPath section]];
6
    NSArray *fruitsForSection = [self.alphabetizedFruits objectForKey:key];
7
    NSString *fruit = [fruitsForSection objectAtIndex:[indexPath row]];
8
9
    NSLog(@"Fruit Selected > %@", fruit);
10
}

Das Abrufen des Namens der Frucht, die der ausgewählten Zeile entspricht, sollte inzwischen bekannt sein. Der einzige Unterschied besteht darin, dass wir den Namen der Frucht in der Xcode-Konsole protokollieren.

Es könnte Sie überraschen, dass wir das  alphabetizedFruits-Wörterbuch verwenden, um die entsprechenden Früchte nachzuschlagen. Warum fragen wir nicht in der Tabellenansicht oder in der Tabellenansichtszelle nach dem Namen der Frucht? Eine Tabellenansichtszelle ist eine Ansicht, deren einziger Zweck darin besteht, dem Benutzer Informationen anzuzeigen. Es weiß nicht, was es anzeigt, außer wie es angezeigt wird. Die Tabellenansicht selbst ist nicht dafür verantwortlich, über ihre Datenquelle Bescheid zu wissen. Sie kann nur die Abschnitte und Zeilen anzeigen, die sie enthält und verwaltet.

Dieses Beispiel ist ein weiteres gutes Beispiel für die Trennung der Bedenken des MVC (Model-View-Controller)-Musters, die wir weiter oben in dieser Serie gesehen haben. Ansichten wissen nichts über Anwendungsdaten, außer wie sie angezeigt werden. Wenn Sie zuverlässige und robuste iOS-Anwendungen schreiben möchten, ist es sehr wichtig, diese Aufgabentrennung zu kennen und zu respektieren.


Schlussfolgerung

Tabellenansichten sind nicht so kompliziert, wenn Sie erst einmal verstanden haben, wie sie sich verhalten, und die beteiligten Komponenten wie die Datenquelle und die delegierten Objekte der Tabellenansicht kennen.

Wir haben jedoch nur einen Blick darauf geworfen, wozu eine Tabellenansicht in der Lage ist. Im Rest dieser Serie werden wir die Tabellenansicht erneut betrachten und einige weitere Teile des Puzzles untersuchen. In der nächsten Folge dieser Serie werden wir uns die Navigationssteuerungen ansehen.