iPhone SDK Tutorial deutsch 4 – UITableView Part 1 – Grundlagen

Hinweis: Dieses Tutorial ist für Xcode Version 4.1 geschrieben und mit der neuesten Version nicht nachvollziehbar. Alle aktuellen Tutorials findest du in der Übersicht.

In diesem und in den nächsten Tutorials wird es um Table Views gehen. 

Da dies ein sehr umfangreiches Thema ist, spalte ich es in mehrere Parts auf. In Part 1 erkläre ich die Grundlagen zu UITableView (Genaueres s. weiter unten).

Wichtiger Hinweis: Dieses Tutorial setzt voraus, dass du dich mit dem Model-View-Controller Prinzip auskennst. Das Tutorial dazu findest du hier: iPhone SDK Tutorial deutsch 3 – Modell-View-Controller.

Hier findest du alle Parts im Überblick:

1. In diesem Tutorial

In diesem Part erstellen wir eine App, die ein Menü eines Spiels darstellen soll (s. Bild oben rechts). Beim Start wird eine Table View angezeigt, in der man ein Spielkonto auswählen kann. Hat man noch keines erstellt, kann man dies über den +-Button tun. Wenn man ein Konto anwählt, startet allerdings nicht das Spiel, sondern es werden Informationen über den Spieler (Account-Name, Level des Spielers und verbleibende Leben) angezeigt.

Ich zeige an dieser Stelle kurz auf, was du in diesem Part lernen wirst:

  • Eine Table View mit eigenen Zeilen anzeigen
  • Auf das Auswählen einer Zeile reagieren
  • Zeilen löschen
  • Neue Zeilen hinzufügen
  • Mit dem Navigation-Controller umgehen

2. Das Projekt erstellen

Anders als bei den bisherigen Tutorials wählen wir diesmal keine View-based Application, sondern eine Navigation-based Application. Daraufhin geben wir noch Namen (Tutorial_4) und Ort des Projekts an.

3. Das Modell erstellen

Wie bei so gut wie jeder App, werden wir auch hier wieder nach dem Modell-View-Controller Prinzip vorgehen. Wir beginnen mit dem Modell.

Klicke mit der rechten Maustaste auf den Ordner Tutorial_4 im Project Navigator und dann auf New File... . Wähle dann Objective-C class und klicke auf Next. Achte nun das Subclass of NSObject angewählt ist. Wir nennen die Klasse Account.

Öffne die Datei Account.h und ändere sich folgendermaßen ab:

#import <Foundation/Foundation.h>

@interface Account : NSObject {
    NSString *_name;
    NSNumber *_level;
    NSNumber *_lifes;
}
@property (nonatomic, copy) NSString *_name;
@property (nonatomic, copy) NSNumber *_level;
@property (nonatomic, copy) NSNumber *_lifes;

-(id) initWithName: (NSString *) newName
             level: (NSNumber *) newLevel
             lifes: (NSNumber *) newLifes;

@end

Alles was hier passiert kennen wir schon aus dem vorherigen Tutorial. Wir erstellen drei Membervariablen für den Namen des Kontos, das Level des Spielers und seine verbleibenden Leben. Außerdem erstellen wir eine Initialisierungs-Methode für die drei Membervariablen.

Auch den Aufbau von Account.m kennen wir schon:

#import "Account.h"

@implementation Account
@synthesize _name, _level, _lifes;

- (id)initWithName:(NSString *)newName level:(NSNumber *)newLevel lifes:(NSNumber *)newLifes
{
    self = [super init];
    if (self) {
        self._name = newName;
        self._level = newLevel;
        self._lifes = newLifes;
    }

    return self;
}

-(void) dealloc {
    self._name = nil;
    self._lifes = nil;
    self._level = nil;
    [super dealloc];
}

@end

In Zeile 4 werden die Variablen synthetisiert, was wir der Einfachheit halber innerhalb einer Zeile tun. Ab Zeile 6 erfolgt die Definition der Init-Methode und bei Zeile 18 beginnt die dealloc-Methode, in welcher die Membervariablen auf nil gesetzt werden.

Damit ist das Modell für diese App fertig.

4. Die Tabelle einrichten

Bevor die Table View funktioniert, müssen wir noch einige Kleinigkeiten abändern.

Beginnen wir damit, ein Array zu erstellen, das alle Konten enthält, die in unserer Tabelle dargestellt werden.
(Ein Array (engl. für Anordnung oder Reihe) ist eine Datenstruktur, also eine Sammlung verschiedener Variablen. Weitere Informationen findest du hier: http://de.wikipedia.org/wiki/Feld_(Datentyp) )
Der RootViewController stellt die Table View da, weshalb wir sämtliche Änderungen an der Tabelle hier vornehmen. Öffne also RootViewController.h und schreibe Folgendes unter die öffnende, geschweifte Klammer:

    NSMutableArray *_accountsArray;

In diesem Fall benötigen wir keine properties und damit müssen wir die Variable auch nicht synthetisieren.
Noch haben wir dem Array kein Objekt hinzugefügt, wenn wir das allerdings gleich tun, benötigen wir die Klasse Account. Deshalb binden wir diese noch ein:

// Unter #import
@class Account;

RootViewController.m:

// Unter #import "RootViewController.h"
#import "Account.h"

Nun ändern wir viewDidLoad ab, also die Methode die aufgerufen wird, wenn die App gestartet wurde und der View fertig geladen ist:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.title = @"Konten";

    _accountsArray = [[NSMutableArray alloc]init];
    Account *firstAccount = [[Account alloc]initWithName:@"ToDelete" level:[NSNumber numberWithInt:1] lifes:[NSNumber numberWithInt:0]];
    [_accountsArray addObject:firstAccount];
    [firstAccount release];
}

Zuerst geben wir dem View einen Namen. Dieser wird oben in der Navigationsleiste angezeigt. Als Nächstes reservieren wir mit alloc Speicher für das eben erstellte Array (Zeile 6). Dann erstellen wir eine Instanz von Account in Zeile 7. Dieses Konto, das beim Start der App schon angezeigt werden wird, dient nur zu Demonstrationszwecken und kann vom User in der App gleich wieder gelöscht werden (sobald wir das Löschen von Zeilen implementiert haben ;-) ). In Zeile 8 schieben wir das gerade erstellte Konto in unser Array. Dieses hat somit sein erstes Element. In Zeile 9 geben wir firstAccount wieder frei, da es nun in unserem Array gespeichert ist und wir es deshalb nicht mehr benötigen.

Jetzt kommen wir zur Methode tableView:numberOfRowsInSection:, welche du ebenfalls in RootViewController.m findest. Ersetze das return 0; durch

    return [_accountsArray count];

Damit weiß die Table View, wie viele Zeilen sie enthält, nämlich genau so viele, wie es Elemente im Array _accountsArray gibt. Folglich erhält man die Anzahl der Elemente eines Arrays durch Aufrufen der count-Funktion.

Als Nächstes müssen wir noch die Funktion tableView:cellForRowAtIndexPath bearbeiten. Relativ am Ende der Funktion steht das Kommentar “Configure the cell.” Das bedeutet, dass dies der Punkt ist, an dem wir angeben, wie eine Zeile in der Tabelle aussehen soll. Schreibe unter das Kommentar:

    Account *anAccount = [_accountsArray objectAtIndex:indexPath.row];
    cell.textLabel.text = anAccount._name;

In dieser Funktion können wir also angeben, wie eine Zeile aussehen soll. Bspw: Ob der Text groß oder klein sein, links oder rechts, mit oder ohne Bild sein soll, usw. Aber natürlich auch welcher Text angezeigt werden soll. Wir können das Aussehen der Zeilen für jede einzeln bestimmen. Mit einem Beispiel lässt sich das am besten erklären: Wenn der View aufgerufen wird, ist die Tabelle noch komplett leer. Allerdings stehen in unserem Array _accountsArray bereits alle Informationen, die in unserer Tabelle stehen sollen. Also wird zuerst die Funktion tableView:numberOfRowsInSection: aufgerufen, damit bekannt ist, wie viele Zeilen die Tabelle enthalten soll. Damit alle Informationen aus _accountsArray in der Tabelle ihren Platz haben, haben wir die Funktion so abgeändert, dass sie die Anzahl der Elemente des Arrays zurückgibt.
Jetzt wird die besagte Funktion tableView:cellForRowAtIndexPath aufgerufen. Sagen wir unsere Tabelle hat 4 Zeilen, somit hat _accountsArray 4 Elemente. Die Funktion tableView:cellForRowAtIndexPath wird also zuerst für die oberste Zeile aufgerufen. Demnach enthält das Argument indexPath den Pfad zur ersten Zeile. Nun erhält die Zeile die richtigen Informationen. Und die befinden sich im _accountsArray ebenfalls an erster Stelle. Es wird ein Konto erstellt, das die Informationen aus dem _accountsArray rausliest. Daraufhin erhält das Textlabel der Zeile diese Informationen. Und das wiederholt sich für die 2., 3. und 4. Zeile.

Jetzt kannst du die App zum ersten Mal ausführen. Du siehst nur die Tabelle mit der Zeile “ToDelete”.

5. Zeilen löschen

Als Nächstes wollen wir dem User erlauben, Zeilen aus der Tabelle zu löschen. Das implementieren wir in der Funktion tableView:commitEditingStyle:forRowAtIndexPath:, in der Datei RootViewController.m. Allerdings müssen zuerst die Kommentarzeichen entfernen.

Ändere sie daraufhin so ab, dass sie wie folgt aussieht:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        // Delete the row from the data source.
        [_accountsArray removeObjectAtIndex:indexPath.row];

        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }
    else if (editingStyle == UITableViewCellEditingStyleInsert)
    {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
    }
}

Alles was hier passiert ist, dass das passende Element aus _accountsArray und die Tabellen-Zeile gelöscht werden.

Wenn du die App nun ausführst kannst du mit einem waagerechten Wisch mit der Maus über die Zeile einen delete-Button erscheinen lassen.

Eine weitere Möglichkeit des Löschens ist der Edit-Button in der Navigationsleiste. Diesen einzufügen ist denkbar einfach. Füge einfach folgende Zeile in die Methode viewDidLoad in RootViewController.m ein:

    self.navigationItem.leftBarButtonItem = self.editButtonItem;

Mit dieser Zeile Code wird angegeben was sich wo auf der Navigationsleiste befindet. In unserem Fall soll es ein Edit-Button auf der linken Seite sein.

6. Auswahl einer Zeile

Als Nächstes soll unsere App auf das Antippen einer Zeile reagieren. Und zwar soll sich daraufhin ein neuer View Controller öffnen, in dem die Informationen über den Account angezeigt werden.

Dazu müssen wir zuerst einen neuen View Controller erstellen. Klicke mit der rechten Maustaste auf den Ordner Tutorial_4 und dann auf New File… Dort wählst du UIViewController subclass und im nächsten Fenster dann Subclass of UIViewController. Wir nennen den neuen View Controller StartGameViewController.

Wir beginnen nun mit dem Interface. Im Großen und Ganzen sollte es so aussehen:

Den Text der drei “Label”-Label ändern wir später im Code. Wichtig ist hierbei noch, dass wir den Rahmen der “Label”-Label größer ziehen, sodass auch größere Texte Platz haben.

Du fragst dich vielleicht, wo sich der Zurück-Button befindet. Wir werden zum Zurückgehen den Navigation-Controller verwenden, aber dazu später mehr.

Wenn wir den Text der Labels ändern wollen, benötigen wir deren Outlets. Diese erstellen wir mit der neuen Funktion von Xcode 4 (s. Tutorial 3). Zuerst müssen wir wieder den Editor in zwei Spalten teilen. Wähle also den mittleren Knopf bei Editor in der Leiste des Fensters. Nun klickst du mit gedrückter ctrl-Taste auf das Label und ziehst den blauen Faden unter die schließende, geschweifte Klammer in StartGameViewController.h. Wir nennen es _nameLabel. Wiederhole dies bei den anderen beiden Labels (_levelLabel, _lifesLabel).

Es folgt nun die Implementierung des neuen View Controllers, wie wir sie schon kennen:
Öffne die Datei RootViewController.h und schreibe unter @class Account; Folgendes:

@class StartGameViewController;

Öffne RootViewController.m und schreibe

#import "StartGameViewController.h"

unter #import “Account.h”.

Nun Wechslern wir zur Datei RootViewController.xib und ziehen von der Library einen View Controller in die “Objects”-Leiste. Lasse dir den View Controller im Identity Inspector (3. Reiter von links, rechte Seitenleiste) anzeigen und gib bei class den Namen unseres neuen View Controller an: StartGameViewController.

Als Nächstes solltest du wieder sicher gehen, dass der Editor gespaltet ist und dass auf der linken Seite RootViewController.xib und auf der rechten Seite RootViewController.h angezeigt wird. Ist dies der Fall, klicken wir mit gedrückter ctrl-Taste auf den View Controller in der Objects-Leiste und ziehen den Faden unter die schließende, geschweifte Klammer. Daraufhin wird ein Outlet des View Controllers erstellt. Wir nennen es _startGameVC.

Nun öffnen wir die Datei StartGameViewController.h und schreiben unter #import <UIKit/UIKit.h>:

@class Account;

In StartGameViewController.m noch das dazugehörige

#import "Account.h"

Jetzt erstellen wir eine Instanz der Klasse Account. Vielleicht fällt dir schon auf, für was wir diesen Account benötigen: Wir werden (wie schon in Tutorial 3) in diesem Account die Informationen speichern, die wir vom RootViewController erhalten haben. Daraufhin werden wir sie dann anzeigen. Aber nun erstmal die Instanz:

StartGameViewController.h:

    Account *_viewedAccount;

// property
@property (nonatomic, retain) Account *_viewedAccount;

StartGameViewController.m:

// snythesize
@synthesize _viewedAccount;

    // In der dealloc-Methode
    [_viewedAccount release];

Jetzt kommen wir zur Methode, die aufgerufen wird, wenn eine Zeile ausgewählt wird: tableView:didSelectRowAtIndexPath. Sie befindet sich in der Datei RootViewController.m. Zwischen den Kommentarzeichen in der Funktion wird ein View Controller per Code erzeugt. Wir haben allerdings schon unseren eigenen View Controller, StartGameViewController, weshalb wir den ganze Kommentar löschen.

Stattdessen übergeben wir hier dem neuen View Controller die Informationen vom Account dieser Zeile und rufen ihn daraufhin auf:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    Account *anAccount = [_accountsArray objectAtIndex:indexPath.row];
    _startGameVC._viewedAccount = anAccount;
    [self.navigationController pushViewController:_startGameVC animated: YES];
}

Bis einschließlich Zeile 4 ist uns alles bekannt. In Zeile 5 rufen wir den View Controller auf. Allerdings verwenden wir dazu den navigationController. Was das zur Folge hat, sieht man, wenn man die App ausführt und eine Zeile anklickt: Oberhalb des Views befindet sich die Navigationsleiste. Zur Demonstration kommentieren wir diese Zeile einmal aus und schreiben stattdessen:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    Account *anAccount = [_accountsArray objectAtIndex:indexPath.row];
    _startGameVC._viewedAccount = anAccount;
    //[self.navigationController pushViewController:_startGameVC animated: YES];
    [self presentModalViewController:_startGameVC animated:YES];
}

So haben wir bisher einen neuen View aufgerufen. Wenn du die App nun ausführst erscheint der neue View ohne die Navigation Bar. Wir wollen unseren neuen View allerdings mit Navigation Bar, also rufen wir ihn mit Hilfe des Navigation Controllers auf.
Jetzt wird auch ersichtlich, wieso wir in StartGameViewController.xib keinen Zurück-Button eingefügt haben. Dadurch, dass wir in der Methode viewDidLoad in RootViewController.m mit self.title = @”Konten”; den Titel des Views gesetzt haben, wird automatisch ein Button zurück zu diesem View Controller erstellt.

Zunächst kommen wir zur Methode viewWillAppear in StartGameViewController.m. Hier ändern wir den Text der Label, sodass sie die Informationen über das Konto anzeigen. So soll die Methode aussehen:

-(void) viewWillAppear:(BOOL)animated {
    _nameLabel.text = _viewedAccount._name;

    NSString *levelString = [[NSString alloc]initWithFormat:@"%i", [_viewedAccount._level intValue]];
    _levelLabel.text = levelString;
    [levelString release];

    NSString *lifesString = [[NSString alloc]initWithFormat:@"%i", [_viewedAccount._lifes intValue]];
    _lifesLabel.text = lifesString;
    [lifesString release];
}

Wir übergeben den Labels die Informationen, die wir vom RootViewController erhalten haben und welche in _viewedAccount gespeichert sind. Da die beiden Letzteren Zahlen sind, müssen wir sie erst in einen String konvertieren.

Jetzt wollen wir auch diesem View noch einen Titel geben. Schreibe also in der Methode viewDidLoad:

    self.title = @"Spiel";

Nun ist unser neuer View Controller vollständig implementiert.

Tipp: Wenn du das Interface des Views in der .xib-Datei erstellst, wird der View ohne Navigation Bar angezeigt. Man kann diese allerdings simulieren, sodass du die Objekte im View so anordnen kannst, dass sie später nicht außerhalb des Displays sind oder von der Tastatur verdeckt werden. Öffne also die .xib-Datei, in unserem Fall StartGameViewController.xib und makiere den View in der Seitenleiste unter Objects. Lasse ihn dir dann im Attributes inspector anzeigen (4. Reiter von links). Unter Simulated Metrics kannst du für Top Bar den Wert Navigation Bar einstellen.

7. Zeilen hinzufügen

Jetzt werden wir einen Plus-Button zur Navigation Bar hinzufügen. Drückt der Benutzer auf diesen Button, so kann er ein neues Konto erstellen. Also kommt eine Zeile zur Tabelle hinzu.

Zu allererst deklarieren wir die Funktion die aufgerufen wird, wenn der Plus-Button gedrückt wird. Öffne also RootViewController.h und schreibe vor @end:

    -(IBAction)handleAddTapped;

Damit wir nicht von Anfang an einen Fehler erhalten, definieren wir diese Methode gleich, ohne etwas zwischen dir geschweiften Klammern zu schreiben:

RootViewController.m:

-(IBAction)handleAddTapped {

}

Jetzt wollen wir den Button in die Navigation Bar einbauen. Das tun wir allerdings nicht in RootViewController.xib, denn dort befindet sich nirgends eine Navigation Bar, sondern wir öffnen MainWindow.xib. In der Seitenleiste, unter Objects, befinden sich drei Objects: Tutorial4 App Delegate, Window und Navigation Controller. Wenn du auf den Pfeil neben Navigation Controller klickst, kommen die Navigation Bar und der RootViewController zum Vorschein. Klickst du nun auf Navigation Bar, erscheint der View mit der Navigation Bar im Editor. Als Nächstes suchen wir in der Library nach einem Bar Button Item. Dieses ziehen wir auf die rechte Seite der Navigation Bar, wo sofort ein für den Button vorhergesehener Platz sichtbar wird. Wieder in der Seitenleiste und Objects klappen wir den RootViewController und schließlich noch das Navigation Item weiter aus. Als unterstes Objekt erscheint das Bar Button Item. Wähle es aus und lass es dir im Attributes inspector anzeigen. Ändere Identifier auf Add. Daraufhin zeigt der Button ein +.

Um nun die Verbindung zwischen dem Button und der Methode herzustellen, müssen wir etwas anders als sonst vorgehen. Bleibe in der Datei MainWindow.xib und wähle das Bar Button Item in der Objects-Leiste aus. Lasse es dir nun im Connections inspector anzeigen. Wie du siehst befindet sich hier nirgends ein Touch Up Inside, sondern als einzige Action selector. Außerdem dürfen wir die Action nicht mit dem File’s Owner verbinden. Das liegt daran, dass die Methode handleAddTapped in der Datei RootViewController.h deklariert wurde. Demnach müssen wir die Action auch mit dem RootViewController in der Objects-Leiste verbinden und dann handleAddTapped auswählen.

Nun müssen wir noch den View Controller erstellen, der aufgerufen werden soll wenn der Plus-Button gedrückt wird. Klicke also mit der rechten Maustaste auf den Ordner Tutorial_4 im Project Navigator. Klicke wieder auf New File… und erstelle dann einen UIViewController subclass. Nenne ihn AddAccountViewController.

Wir beginnen mit dem Interface. Die Datei AddAccountViewController.xib sollte danach etwa so aussehen:

Wir benötigen nun das Outlet des Textfeldes und die Action Buttons. Spalte also den Editor und klicke bei gedrückter ctrl-Taste auf das Textfeld. Ziehe den blauen Faden unter die geschweifte Klammer in AddAccountViewController.h. Nenne das Outlet _nameField. Zeige jetzt den Button im Connections inspector an und ziehe den Faden von Touch Up Inside über @end, ebenfalls in AddAccountViewController.h. Die Action soll done heißen.

Als Nächstes müssen wir den neuen View Controller im Interface Builder initialisieren. Öffne RootViewController.xib und ziehe einen View Controller aus der Library in die Objects-Leiste. Wähle ihn nun aus und ändere im Identity inspector die Klasse des View Controllers auf AddAccountViewController. Um das Outlet des View Controllers zu erstellen gehen wir wieder sicher das der Editor gespalten ist. Nach dem Klick bei gedrückter ctrl-Taste auf den View Controller in der Objects-Leiste ziehen wir den Faden unter die geschweifte Klammer in RootViewController.h. Nenne ihn _addAccountVC. Damit keine Fehler auftreten binden wir nun noch die Header-Datei des neuen View Controllers ein.

RootViewController.h:

// Unter die anderen @class
@class AddAccountViewController;

RootViewController.m:

// Unter die anderen #import
#import "AddAccountViewController.h"

Nun widmen wir uns den eben erstellte View Controller. Zuerst binden wir auch in diesen View Controller das Model ein, also die Klasse Account.

AddAccountViewController.h:

// Unter #import
@class Account;

AddAccountViewController.m:

// Unter das andere #import
#import "Account.h"

Als nächstes implementieren wir das Textfield Delegate. Öffne also zunächst AddAccountViewController.h und schreibe in der @intreface-Zeile vor die geschweifte Klammer Folgendes:

<UITextFieldDelegate>

Und noch die Funktion implementieren, sodass sich die Tastatur schließen lässt:
AddAccountViewController.m

// Zu den anderen Funktionen
-(BOOL) textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return TRUE;
}

Damit das Delegate weiß, zu welchem Textfeld es gehört, öffnen wir die Datei AddAccountViewController.xib, wählen das Textfeld aus, betrachten es im Connections inspector und ziehe den Faden von delegate zum File’s Owner.

Jetzt kommen wir zum eigentlich wichtigen Teil dieses Abschnittes: Der neuen Zeile. Zu allererst kümmern wir uns um die Informationen die in der Zeile stehen sollen. Das beginnt damit, dass der Nutzer etwas im Textfeld des AddAccountViewControllers eingibt. Der Name, den der Nutzer also eingibt, soll in einer Instanz der Klasse Account gespeichert werden. Diese Objekt erstellen wir jetzt (weshalb wir auch die Klasse Account eingebunden haben):

AddAccountViewController.h:

    // Unter die erste Membervariable
    Account *_newAccount;
// Unter die erste Zeile mit @property
@property (nonatomic, retain) Account *_newAccount;

AddAccountViewController.m:

// Unter die erste Zeile @synthesize
@synthesize _newAccount;

// In die dealloc-Methode
[_newAccount release];

Nun geht es darum, dass der Name aus dem Textfeld auch in diesem Konto landet. Und dazu definieren wir eine weitere Funktion aus dem UITextFieldDelegate: textFieldDidEndEditing. In ihr weisen wir dem Konto den Text des Textfields zu. Allerdings müssen wir ebenfalls das Level und die Anzahl der Leben festlegen, die beim ersten 1 und beim zweiten 3 sind. Zum Schluss der Methode leeren wir noch das Textfeld.

-(void) textFieldDidEndEditing:(UITextField *)textField {
    self._newAccount._name = self._nameField.text;

    self._newAccount._level = [NSNumber numberWithInt:1];

    self._newAccount._lifes = [NSNumber numberWithInt:3];

    self._nameField.text = @"";
}

Bevor wir uns jetzt wieder dem RootViewController zuwenden, um die neue Zeile zu erstellen, definieren wir noch die Action des Fertig-Buttons. Du findest die Methode bereits in AddAccountViewController.m am Ende der Datei. Sie sollte nun so aussehen:

- (IBAction)done:(id)sender {
    [self dismissModalViewControllerAnimated:YES];
}

Wir schließen den View Controller mit dismissModalViewController.

Ich werde nun das Prinzip erläutern, nach dem wir vorgehen, um ein neues Konto hinzuzufügen. Wenn der Plus-Button gedrückt wird, wird die Methode handleAddTapped aufgerufen. Nun erstellen wir in dieser Methode eine neue Instanz von Account, um dort dann die Informationen aus dem AddAccountViewController zu speichern und dann mit diesen Informationen eine neue Zeile zu erstellen. Wir erstellen also die Instanz und rufen dann den neuen View Controller auf. Daraufhin erstellen wir die Zeile. Es gibt allerdings ein Problem: Auch wenn der Aufruf der des View Controllers vor dem Erstellen der Zeile im Code steht, so wird trotzdem erst die ganze Funktion bis zum Ende durchgearbeitet und dann erst der View Controller aufgerufen. Das Problem liegt auf der Hand: Der Account mit dem wir die Zeile erstellen wollten, wäre leer. Hier einmal am Code gezeigt.

-(IBAction)handleAddTapped{
    // Den Account erstellen
    // hier ist der Account noch leer

    // den View Controller aufrufen
    // In diesem View Controller erhält der Account die Informationen

    // Die Zeile erstellen
    // Problem: Account ist immer noch leer, da der Aufruf erst jetzt statt findet
    /// Eigentlicher Aufruf des View Controllers
}

Die Lösung des Problems ist Folgende: Wir fügen dem RootViewController eine Instanz von Account hinzu, in der wir während der Methode handleAddTapped die Informationen aus dem AddAccountViewController speichern. In der Methode werden die Vorbereitungen für das Erstellen der Zeile schon getroffen. Die Informationen erhält die Zeile allerdings erst in einer anderen Methode, wenn der AddAccountViewController schon fertig ist und alle Informationen in der Instanz von Account gespeichert wurden. Und diese Methode, in der wir die Zeile erstellen, ist viewWillAppear. Wie wir wissen, wird sie jedes Mal aufgerufen, wenn der View erscheint. Das ist perfekt, da wir nur prüfen müssen, ob die Instanz von Account bearbeitet wurde, also Informationen enthält, und dann die Zeile erstellen können.

Wir beginnen also damit dem RootViewController die Instanz von Account hinzuzufügen:

RootViewController.h:

    // zu den anderen Membervariablen
    Account *_editingAccount;

// Zu den anderen @property-Zeilen
@property (nonatomic, retain) Account *_editingAccount;

RootViewController.m:

// Zu den anderen @synthesize-Zeilen
@synthesize _editingAccount;
    // in der dealloc-Methode
    [_editingAccount release];

Jetzt kommen wir zur Funktion handleAddTapped:

-(IBAction)handleAddTapped {
    Account *newAccount = [[Account alloc] init];
    _editingAccount = newAccount;
    _addAccountVC._newAccount = _editingAccount;

    [self presentModalViewController:_addAccountVC animated:YES];

    [_accountsArray addObject:newAccount];
    NSIndexPath *NewAccountPath =[NSIndexPath indexPathForRow:[_accountsArray count]-1 inSection:0];
    NSArray *newAccountPaths = [NSArray arrayWithObject:NewAccountPath];
    [self.tableView insertRowsAtIndexPaths:newAccountPaths withRowAnimation:NO];
    [newAccount release];
}

In Zeile 3 weisen wir der eben erstellten Membervariable _editingAccount den Wert eines vorher erstellen Accounts zu. Damit gehen wir sicher, dass später in viewWillAppear eine neue Zeile erstellt wird. Daraufhin übergeben wir _editingAccount dem AddAccountViewController. Nachdem dieser aufgerufen wurde treffen wir die Vorbereitungen zum Hinzufügen einer neuen Zeile. Wir geben an, an welchem Platz wir sie erstellen wollen und erstellen schon eine leere Zeile.

Jetzt, in viewWillAppear, erhält die Zeile die Informationen:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    if (_editingAccount) {
        _editingAccount = _addAccountVC._newAccount;
        NSIndexPath *updatedPath = [NSIndexPath indexPathForRow:[_accountsArray indexOfObject:_editingAccount] inSection:0];
        NSArray *updatedPaths = [NSArray arrayWithObject:updatedPath];
        [self.tableView reloadRowsAtIndexPaths:updatedPaths withRowAnimation:NO];
        _editingAccount = nil;
    }
}

Wir überprüfen zuerst, ob überhaupt ein neues Element hinzugefügt werden soll. Dann übergeben wir die Informationen und updaten diese Zeile, die nun die Informationen aus dem AddAccountViewController besitzt, mit der Methode reloadRowsAtIndexPaths:withRowAnimation. Wichtig ist noch, _editingAccount auf nil zu setzen, damit beim nächsten Durchlauf nicht wieder eine neue Zeile erstellt wird.

Du kannst die App nun ausführen und alles ausprobieren. Du kannst also Zeilen löschen, auswählen und hinzufügen.

8. Feldstile

Zum Schluss möchte ich noch zeigen, wie man verschieden Stile für die Zeilen benutzt. Du kannst in der Funktion tableView:cellForRowAtIndexPath: angeben, welcher Stil benutz werden soll. Innerhalb der geschweiften Klammern von if findest du die Methode initWithStyle:reuseIdentifier: Beim ersten Argument, initWithStyle, sollte UITableViewCellStyleDefault stehen. Wenn du das löscht und beginnst UITableViewCellStyle… zu schreiben, so schlägt dir Xcode die verschieden Stile vor:

Wir verwenden zur Demonstration UITableViewCellStyleValue1. Bei allen Feldstilen, außer bei UITableViewCellStyleDefault, wird ein detailTextLabel benutzt. Je nach Feldstil wird es an einer anderen Stelle der Zeile angezeigt. Wir benutzten es, um das Level des Spielers, dem der Account gehört, anzuzeigen. Wir definieren das detailTextLabel an der selben Stellen, an der wir das textLabel definiert haben. Schreibe also Folgendes unter cell.textLabel.text = anAccount._name; :

    cell.detailTextLabel.text = [anAccount._level stringValue];

Die Methode tableView:cellForRowAtIndexPath: sieht dann also so aus:

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
    }

    // Configure the cell.
    Account *anAccount = [_accountsArray objectAtIndex:indexPath.row];
    cell.textLabel.text = anAccount._name;
    cell.detailTextLabel.text = [anAccount._level stringValue];
    return cell;
}

Nun wird am rechten Rande der Zeile das Level angezeigt. Du kannst jetzt auch noch die anderen Feldstile ausprobieren.

9. Schluss

Das war der erste Part von Tutorial 4. Ich werde voraussichtlich noch ein oder mehrere weitere Tutorials zu diesem Thema schreiben.

Hier könnt ihr den Code downloaden: iPhone_SDK_Tutorial_deutsch_4-UITableView_Part1-Grundlagen

Ich freue mich auf eure Kommentare!

This entry was posted in iPhone SDK, Tutorial and tagged , . Bookmark the permalink.

11 Responses to iPhone SDK Tutorial deutsch 4 – UITableView Part 1 – Grundlagen

  1. klaus says:

    Hi

    in viewWillAppear scheinen die Properties in _addAccountVC._newAccount nicht belegt zu sein. Ich würde natürlich gerne _accountsArray auch immer synchron mit TableView haben, frage mich nun aber was da passiert und wie ich den neuen Eintrag auch im Array haben kann (ich nutzt Xcode 4.2)

    Gruß
    Klaus

    • Felix says:

      Hallo,

      ich glaube ich verstehe, was Du meinst. Es sollen im _accountsArray immer so viele Elemente enthalten sein, wie die Table View Zeilen hat.
      Das ist so auch der Fall. Ich denke Du wolltest in viewWillAppear so etwas machen:

      - (void)viewWillAppear:(BOOL)animated
      {
          [super viewWillAppear:animated];
          if (_editingAccount) {
              _editingAccount = _addAccountVC._newAccount;
              // [_accountsArray addObject: _addAccountVC._newAccount]; // Diese Zeile ist falsch
              NSIndexPath *updatedPath = [NSIndexPath indexPathForRow:[_accountsArray indexOfObject:_editingAccount] inSection:0];
              NSArray *updatedPaths = [NSArray arrayWithObject:updatedPath];
              [self.tableView reloadRowsAtIndexPaths:updatedPaths withRowAnimation:NO];
              _editingAccount = nil;
          }
      }

      Damit erhält man allerdings einen Runtime-Error.
      Die oben markierte Zeile ist aber gar nicht nötig um Array und Table View synchron zu halten, das passiert automatisch. Um das zu testen füge am Ende von viewWillAppear einfach eine NSLog-Anweisung ein:

      - (void)viewWillAppear:(BOOL)animated
      {
          [super viewWillAppear:animated];
          if (_editingAccount) {
              _editingAccount = _addAccountVC._newAccount;
              NSIndexPath *updatedPath = [NSIndexPath indexPathForRow:[_accountsArray indexOfObject:_editingAccount] inSection:0];
              NSArray *updatedPaths = [NSArray arrayWithObject:updatedPath];
              [self.tableView reloadRowsAtIndexPaths:updatedPaths withRowAnimation:NO];
              _editingAccount = nil;
              NSLog(@"%i", [_accountsArray count]);
          }
      }

      Jetzt wird dir jedes mal wenn Du eine neue Zeile hinzufügst die Anzahl der Elemente im Array angezeigt(in der Konsole). Und die ist immer gleich der Anzahl der Table View Zeilen.

      Ich hoffe ich habe das richtig verstanden. Wenn ich deine Frage noch nicht richtig beantwortet habe, kannst Du dich ja nochmal melden.

      Grüße
      Felix

  2. Pingback: iPhone SDK Tutorial deutsch 5 – Tab Bar Controller | iOsDevGermany

  3. Pingback: iPhone SDK Tutorial deutsch 4 – UITableView Part 3 – Eigene Tabellenfelder | iOsDevGermany

  4. Pingback: iPhone SDK Tutorial deutsch 4 – UITableView Part 2 – Sectioned Table View | iOsDevGermany

  5. Kosche says:

    Ich bin jetzt durch und der Code ist sauber. :-)

    Was mir noch aufgefallen ist, das UITextfield ist mit dem alten Wert gefüllt, wenn man den “+” Button wieder drückt. Das läßt sich leicht beheben mit:

    -(void) textFieldDidEndEditing:(UITextField *)textField {
    ...
    self._nameField.text = @"";
    }

    Was mir beim Debuggen aufgefallen ist, er kommt gar nicht in den “dealloc” Aufruf in AddAccountViewController. Ist das bei Dir auch so? Das wundert mich jetzt schon ein bisschen.

    Such mal in Deinem Text nach “weißem” ;-)

    Grüße
    Kosche

    • Felix says:

      Hi,
      wiedermal Danke für das gründliche Durchforsten des Tutorials :-) .

      Zu 2.: Tatsächlich wird dealloc nicht aufgerufen. Ich habe leider bisher noch keine Lösung gefunden, aber ich werde es sobald wie möglich beheben.

      mfg.
      Felix

  6. kosche says:

    Hi Felix,

    schöner Beitrag. Bin noch nicht ganz durch, hänge noch beim Add rum. Aber bis dahin gefällt es mir.

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*


fünf × 5 =

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>