Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Geleitwort
Vorwort
1 Hello iPhone
2 Grundlagen
3 Views und Viewcontroller
4 Alles unter Kontrolle
5 Daten, Tabellen und Controller
6 Models, Layer, Animationen
7 Programmieren, aber sicher
8 Datenserialisierung und Internetzugriff
9 Jahrmarkt der Nützlichkeiten
A Sicherer Entwicklungszyklus
Stichwort

Download:
- ZIP, ca. 49,9 MB
Buch bestellen
Ihre Meinung?

Spacer
Apps programmieren für iPhone und iPad von Klaus M. Rodewig, Clemens Wagner
Das umfassende Handbuch
Buch: Apps programmieren für iPhone und iPad

Apps programmieren für iPhone und iPad
Galileo Computing
1000 S., geb., mit DVD
39,90 Euro, ISBN 978-3-8362-1915-0
Pfeil 5 Daten, Tabellen und Controller
Pfeil 5.1 Benachrichtigungen
Pfeil 5.1.1 Benachrichtigungen empfangen
Pfeil 5.1.2 Eigene Benachrichtigungen verschicken
Pfeil 5.2 Layoutanpassungen und Viewrotationen
Pfeil 5.2.1 Flexible Views dank Autosizing
Pfeil 5.2.2 Lang lebe das Rotationsprinzip!
Pfeil 5.2.3 Anpassung des Layouts
Pfeil 5.3 Autolayout
Pfeil 5.3.1 Restriktionen im Interface Builder festlegen
Pfeil 5.3.2 Restriktionen per Programmcode erstellen
Pfeil 5.3.3 Autolayout und Lokalisierung
Pfeil 5.4 Core Data
Pfeil 5.4.1 Datenmodellierung
Pfeil 5.4.2 Implementierung von Entitätstypen
Pfeil 5.4.3 Einbindung von Core Data
Pfeil 5.4.4 Der Objektkontext
Pfeil 5.4.5 Die Nachrichten des Objektkontexts
Pfeil 5.4.6 Anlegen und Ändern von Entitäten in der Praxis
Pfeil 5.5 Texte, Bilder und Töne verwalten
Pfeil 5.5.1 Die Tastatur betritt die Bühne
Pfeil 5.5.2 Fotos aufnehmen
Pfeil 5.5.3 Töne aufnehmen und abspielen
Pfeil 5.6 Tableviews und Core Data
Pfeil 5.6.1 Tableviews
Pfeil 5.6.2 Tabellenzellen gestalten
Pfeil 5.6.3 Tableviews unter iOS 5
Pfeil 5.6.4 Buttons in Tabellenzellen
Pfeil 5.6.5 Zellen löschen
Pfeil 5.7 Core Data II: Die Rückkehr der Objekte
Pfeil 5.7.1 Prädikate
Pfeil 5.7.2 Aktualisierung des Tableviews
Pfeil 5.7.3 Das Delegate des Fetchedresultscontrollers
Pfeil 5.7.4 Tabelleneinträge suchen
Pfeil 5.8 Twitter
Pfeil 5.8.1 Der Twitter-Account
Pfeil 5.8.2 Hinzufügen des Twitter-Frameworks
Pfeil 5.8.3 Integration in das Fototagebuch
Pfeil 5.9 Collectionviews
Pfeil 5.9.1 Der Collectionviewcontroller
Pfeil 5.9.2 Gitterdarstellung
Pfeil 5.9.3 Zellen und die Datenquelle
Pfeil 5.9.4 Ergänzende Views
Pfeil 5.9.5 Freie Layouts
Pfeil 5.9.6 Freie Layouts und ergänzende Views
Pfeil 5.9.7 Zellen auswählen
Pfeil 5.9.8 Decorationviews

Galileo Computing - Zum Seitenanfang

5.7 Core Data II: Die Rückkehr der ObjekteZur nächsten Überschrift

Mit den in Abschnitt 5.4 beschriebenen Methoden können Sie einen Objektgraphen erzeugen, verändern und sichern. Um die Daten im Tableview anzuzeigen, benötigen Sie noch eine Möglichkeit, um Teile des Objektgraphen aus der Datenhaltung wieder in den Objektkontext zu laden. Mit der Methode executeFetchRequest:error: können Sie Entitäten des gleichen Typs in der Datenhaltung suchen und laden.

Suchanfragen in Core Data beschreiben Sie über Objekte der Klasse NSFetchRequest. Eine Suchanfrage sucht immer nur die Objekte eines Entitätstyps. Dabei sucht Core Data auch nach Entitäten der Untertypen. Sie können dabei die Ergebnismenge natürlich durch eine Bedingung einschränken. Diese Prädikate können Sie über einen Objektbaum oder – was einfacher ist – über eine Zeichenkette formulieren. Sie erhalten das Ergebnis einer Suchanfrage in einem Array. Die Anordnung der Objekte im Array können Sie dabei in der Suchanfrage über eine Sortierreihenfolge bestimmen.

Sie erzeugen eine neue Suchanfrage, indem Sie ein Objekt der Klase NSFetchRequest anlegen und konfigurieren. Die Klasse PhotoDiaryViewController verwendet die folgende Suchanfrage, um die Tagebucheinträge im Tableview anzuzeigen:

- (NSFetchRequest *)fetchRequest {
NSFetchRequest *theFetch = [[NSFetchRequest alloc] init];
NSEntityDescription *theType = Zeilenumbruch
[NSEntityDescription entityForName:@"DiaryEntry"Zeilenumbruch
inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *theDescriptor = [NSSortDescriptorZeilenumbruch
sortDescriptorWithKey:@"creationTime" ascending:NO];
theFetch.entity = theType;
theFetch.sortDescriptors =Zeilenumbruch
[NSArray arrayWithObject:theDescriptor];
return theFetch;
}

Listing 5.57 Erzeugung einer Suchanfrage ohne Prädikat

Für die Konfiguration der Suchanfrage benötigen Sie den Entitätstyp, den ein Objekt der Klasse NSEntityDescription beschreibt. Es ist im Datenmodell enthalten, und Sie können es am einfachsten über eine Klassenmethode der Klasse NSEntityDescription ermitteln.

Die Sortierreihenfolge beschreiben Sie durch ein Array mit Objekten der Klasse NSSortDescriptor. Diese enthalten jeweils den Namen des zu sortierenden Attributs und einen booleschen Wert für die Sortierrichtung. Dabei steht YES für auf- und NO für absteigend.

Über die Methode setPredicate: können Sie eine Suchbedingung in der Anfrage setzen und nur nach Einträgen suchen, die darauf passen. Der Tableview auf der Startseite des Fototagebuchs zeigt immer alle Einträge an, sodass die Anfrage keine Suchbedingung braucht. Die Suche eines Textes über das Suchfeld verwendet hingegen ein Prädikat.


Galileo Computing - Zum Seitenanfang

5.7.1 PrädikateZur nächsten ÜberschriftZur vorigen Überschrift

Ein Prädikat beschreibt eine Bedingung, die Sie auf Objekte anwenden können. Die Auswertung liefert einen booleschen Wert, der angibt, ob die Bedingung für das Objekt wahr oder falsch ist. Am einfachsten formulieren Sie ein Prädikat über eine Zeichenkette. Dazu besitzt die Klasse NSPredicate den Convenience-Konstruktor predicateWithFormat:. Sie können beispielsweise alle Bilder mit dem Prädikat

[NSPrecicate predicateWithFormat:@"type = 'image'"]

aus den Medien herausfiltern. Zeichenketten müssen Sie wie im Beispiel durch einfache oder doppelte Anführungszeichen maskieren.

In den meisten Fällen sind Prädikate jedoch nicht statisch, sondern besitzen Parameter. Sie können in der Zeichenkette – ähnlich wie beim NSString-Konstruktor stringWithFormat: – Platzhalter verwenden:

  • Der Platzhalter %@ steht für ein beliebiges Objekt. Sie können hierfür also Zahlen, Zeichenketten, Datumswerte usw. einsetzen. Sie dürfen allerdings keine primitiven Typen wie int, float oder double verwenden. Diese Werte müssen Sie erst in ein NSNumber-Objekt verpacken.
  • Alternativ können Sie für die primitiven Datentypen die bekannten Platzhalter %d, %f, %u usw. verwenden, die Sie von stringWithFormat: oder NSLog kennen.
  • Wenn Sie Attributnamen oder -pfade als Parameter in dem Prädikat verwenden möchten, müssen Sie den Platzhalter %K verwenden.

Dazu ein paar Beispiele:

// Prädikat aus dem vorherigen Beispiel:
[NSPrecicate predicateWithFormat:@"type = %@", theType]
// Falsch, primitive Datentypen sind mit %@ nicht erlaubt:
[NSPrecicate predicateWithFormat:@"age = %@", 5]
// Richtig, Zahl als NSNumber...
NSNumber *theAge = [NSNumber numberWithInt:5];
[NSPrecicate predicateWithFormat:@"age = %@", theAge]
// ... oder mit %d
[NSPrecicate predicateWithFormat:@"age = %d", 5]
// Angabe des Attributnamens als Parameter
NSString *theAttributeName = @"type";
[NSPrecicate predicateWithFormat:@"%K = %@", theAttributeNameZeilenumbruch
theType]

Listing 5.58 Beispiele für die Prädikaterzeugung

Innerhalb eines Prädikats können Sie die üblichen Vergleichsoperatoren (siehe Tabelle 5.3) verwenden.

Tabelle 5.3 Einfache Vergleichsoperatoren für Prädikate

Operator(en) Vergleich Operator(en) Vergleich

=, ==

Gleich

!=, <>

Ungleich

<

Kleiner

<=

Kleiner gleich

>

Größer

>=

Größer gleich

Mit dem Operator BETWEEN können Sie prüfen, ob ein Wert in einem Bereich liegt. Wenn Sie den Bereich als Parameter angeben möchten, müssen Sie dafür ein Array verwenden:

[NSPrecicate predicateWithFormat:@"age BETWEEN {3, 5}"]
NSArray *theLimits = [NSArray arrayWithInt:[NSNumber
numberWithInt:3], [NSNumber numberWithInt:5], nil];
[NSPrecicate predicateWithFormat:@"age BETWEEN %@",
theLimits]

Listing 5.59 Prädikate mit Bereichen

Für Zeichenketten gibt es spezielle Operatoren:

Tabelle 5.4 Vergleichsoperatoren für Zeichenketten

Operator Vergleich

BEGINSWITH

Die Zeichenkette beginnt mit dem Wert auf der rechten Seite.

ENDSWITH

Die Zeichenkette endet mit dem Wert auf der rechten Seite.

CONTAINS

Die Zeichenkette enthält den Wert auf der rechten Seite.

LIKE

Die Zeichenkette stimmt mit dem Wert auf der rechten Seite überein, der die Wildcards ? und * enthalten darf. Dabei steht ? für genau ein Zeichen und * für beliebig viele Zeichen.

MATCHES

Die Zeichenkette passt auf den regulären Ausdruck auf der
rechten Seite.

Diese Operatoren unterscheiden zwischen Groß- und Kleinschreibung. Sie können an die Operatoren [c] oder [cd] anfügen, um Vergleiche unabhängig von der Schreibweise zu machen. Mit dem Suffix [c] gilt das allerdings nur für die 26 Buchstaben des Alphabets, während [cd] auch die diakritischen Zeichen (Umlaute, Buchstaben mit Akzent) einschließt. Beispielsweise prüft text CONTAINSall’, ob das Attribut text die Zeichenkette »all« enthält. Dieses Prädikat ist für die Zeichenketten »Kalle« und »Weltall« wahr, jedoch für »Alle Vögel« falsch. Um die letzte Zeichenkette einzuschließen, müssen Sie das Prädikat so erzeugen: text CONTAINS[cd]all’.

Tipp

Verwenden Sie im Deutschen immer das Suffix [cd], wenn Sie unabhängig von der Schreibweise vergleichen wollen.

Sie können Vergleiche durch die binären booleschen Operatoren AND und OR miteinander verknüpfen, durch den unären Operator NOT negieren und durch runde Klammern gruppieren.

text CONTAINS[cd] ’all’ AND NOT (age = 3 OR age = 7)

Sie können mit den Prädikaten jedoch nicht nur die Attribute eines Objekts überprüfen, sondern auch die Attribute der über Relationships verbundenen Objekte mit einbeziehen. Bei To-One-Relationships schreiben Sie einfach den Relationship-Pfad in die Bedingung. Wenn Sie beispielsweise alle Medien suchen wollen, deren Tagebucheintrag vor einem bestimmten Datum angelegt wurde, dann können Sie das Prädikat so formulieren: diaryEntry.creationTime < %@.

To-Many-Relationships verweisen in der Regel auf mehrere Attributwerte. Im Prädikat müssen Sie deswegen durch einen Operator angeben, wie die Bedingung auf die Attribute zutreffen soll.

Tabelle 5.5 Operatoren für To-Many-Relationships

Operator Bedeutung

ANY, SOME

Mindestens ein Attribut muss die Bedingung erfüllen.

ALL

Alle Attribute müssen die Bedingung erfüllen.

NONE

Kein Attribut darf die Bedingung erfüllen.

Sie können über ANY media.type = 'image' alle Tagebucheinträge suchen, die mindestens ein Bildmedium haben. Über das Prädikat ALL media.type = 'image' finden Sie hingegen nur Einträge, bei denen alle Medien Bilder sind. Um nur Einträge ohne Bildmedien zu finden, können Sie das Prädikat NONE media.type = 'image' verwenden.

Wie Sie ein Prädikat in einer Core-Data-Suche verwenden, haben Sie bereits erfahren. Sie weisen es einfach über die Methode setPredicate: dem FetchRequest zu. Sie können Prädikate auch über die Methode filteredArrayUsingPredicate: auf Arrays anwenden. Damit können Sie sehr einfach nach Objekten in Arrays suchen, ohne eine Schleife schreiben zu müssen.


Galileo Computing - Zum Seitenanfang

5.7.2 Aktualisierung des TableviewsZur nächsten ÜberschriftZur vorigen Überschrift

Sie haben jetzt die wichtigsten Bausteine, um die Einträge des Fototagebuchs in einem Tableview anzeigen zu lassen. Sie können die Datenquelle über eine Suchanfrage und ein Array implementieren. Über den Objektkontext und die Suchanfrage laden Sie die Objekte in ein Array, das der Controller als Property hält. Die Methoden tableView:numberOfRowsInSection: und entryForTableView: atIndexPath: implementieren Sie dann auf Basis dieses Arrays. Der Tableview für die Anzeige der Suchergebnisse verwendet dieses Vorgehen – aber dazu später mehr.

Bei diesem Vorgehen können Sie den Tableview einfach neu laden, um die Anzeige nach dem Löschen oder Hinzufügen von Einträgen zu aktualisieren. Das ist der einfachste Weg. Dieses Vorgehen gibt dem Nutzer jedoch leider keine optische Rückmeldung über seine letzte Aktion.

Alternativ können Sie die Aktualisierungen auch im Array und im Tableview ausführen. Dann müssen Sie beispielsweise nach dem Hinzufügen eines Eintrags über Core Data auch diesen Eintrag zu Ihrem Array und dem Tableview hinzufügen. Dabei müssen Sie den neuen Eintrag an der gleichen Position im Tableview und im Array einfügen, die der Eintrag auch im entsprechenden Suchergebnis hat.

Core Data stellt Ihnen die Klasse NSFetchedResultsController zur Verfügung, die Ihnen diese Verbindung zwischen Suchanfragen und Tableviews vereinfacht. Außerdem unterstützt sie auch die Unterteilung der Daten in Abschnitte anhand eines Attributs.

Sie erzeugen einen neuen Controller über die Initializer-Methode initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:. Dabei müssen Sie eine Suchanfrage und einen Objektkontext angeben. Für die Parameter für den Keypath des Abschnittsnamens und für den Cachenamen dürfen Sie nil verwenden. Wenn Sie einen Key-Path angeben, unterteilt der Controller die Einträge in Abschnitte. Dabei ermittelt er für jeden Eintrag über den Keypath den Namen des Abschnitts, zu dem der Eintrag gehört.

Der Controller kann seine Ergebnisse in einem Cache speichern, wenn Sie einen Cachenamen angeben. Falls Sie mehrere Fetchedresultscontroller innerhalb einer App verwenden, müssen Sie für jeden Cache einen anderen Namen verwenden.

Die Klasse PhotoDiaryViewController verwendet einen Fetchedresultscontroller und hält ihn in der privaten Property fetchedResultsController. Der Viewcontroller initialisiert ihn in der Methode viewDidLoad und setzt sich selbst als dessen Delegate. Anschließend füllt sie den Controller über einen Aufruf der Methode performFetch:. Das ist die einzige Stelle in der Klasse, an der der Viewcontroller einen Fetch ausführt.

Nach dem Fetch können Sie auf alle gefundenen Objekte über die Property fetchedObjects zugreifen. Auf ein einzelnes Objekt im Ergebnis können Sie über die Methode objectAtIndexPath: zugreifen, wozu Sie die Indexpath-Objekte Ihres Tableviews verwenden können. Die einzelnen Abschnitte liefert der Controller über die Property sections, wobei jedes Element in dem Array das Protokoll NSFetchedResultsSectionInfo implementiert.

Das Protokoll deklariert Propertys, mit denen Sie die Daten des Abschnitts erhalten. Die Property objects liefert die Einträge des Abschnitts, und numberOfObjects liefert deren Anzahl. Den Titel des Abschnitts können Sie über die Property name abfragen. Mit dem Fetchedresultscontroller können Sie die Datenquelle eines Tableviews recht einfach implementieren, wenn Sie den Controller in einer Property halten:

- (NSInteger)numberOfSectionsInTableView:Zeilenumbruch
(UITableView *)inTableView {
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)inTableViewZeilenumbruch
numberOfRowsInSection:(NSInteger)inSection {
id<NSFetchedResultsSectionInfo> theInfo =Zeilenumbruch
[[self.fetchedResultsController sections]Zeilenumbruch
objectAtIndex:inSection];
return [theInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)inTableViewZeilenumbruch
cellForRowAtIndexPath:(NSIndexPath *)inIndexPath {
DiaryEntry *theEntry = [self.fetchedResultsControllerZeilenumbruch
objectAtIndexPath:inIndexPath];
...
}

Listing 5.60 Datenquelle mit einem Fetchedresultscontroller

Diese Implementierung funktioniert sowohl für Tabellen mit als auch ohne Segmentierung. Falls Sie Ihre Daten in Abschnitte unterteilen möchten, sollten Sie noch die Methode tableView:titleForHeaderInSection: der Datenquelle implementieren, was Sie auch über den Controller machen können:

- (NSInteger)tableView:(UITableView *)inTableViewZeilenumbruch
titleForHeaderInSection:(NSInteger)inSection {
id<NSFetchedResultsSectionInfo> theInfo =Zeilenumbruch
[[self.fetchedResultsController sections]Zeilenumbruch
objectAtIndex:inSection];
return [theInfo name];
}

Listing 5.61 Implementierung des Abschnittstitels

Der Fetchedresultscontroller muss natürlich mitbekommen, wenn die App die Tagebucheinträge verändert. Das muss allerdings nicht unbedingt in dem Objektkontext des Fetchedresultscontrollers geschehen. Ebenso kann ja die App Objekte über einen anderen Objektcontroller ändern. Der Controller muss also immer seine Daten aktualisieren, wenn irgendein Objektkontext seine Objekte sichert.

Dazu verwendet die App die Did-Save-Benachrichtigungen der Klasse NSManagedObjectContext. Der Photodiaryviewcontroller registriert nach dem Laden seines Views die Methode managedObjectContextDidSave: im Notificationcenter für diesen Benachrichtigungstyp.

NSNotificationCenter *theCenter = Zeilenumbruch
[NSNotificationCenter defaultCenter];

[theCenter addObserver:selfZeilenumbruch
selector:@selector(managedObjectContextDidSave:)Zeilenumbruch
name:NSManagedObjectContextDidSaveNotificationZeilenumbruch
object:nil];

Listing 5.62 Registrierung für die Did-Save-Benachrichtigung

Über die Methode mergeChangesFromContextDidSaveNotification: können Sie die Änderungen, die ja das Userinfo-Dictionary der Benachrichtigung enthält, direkt in den Objektkontext des Fetchedresultscontrollers importieren. Das sollten Sie natürlich nur machen, wenn die Benachrichtigung nicht von diesem Controller stammt.

- (void)managedObjectContextDidSave:Zeilenumbruch
(NSNotification *)inNotification {
if(inNotification.object != self.managedObjectContext) {
[self.managedObjectContextZeilenumbruch
mergeChangesFromContextDidSaveNotification:Zeilenumbruch
inNotification];
}
}

Listing 5.63 Import der Änderungen aus einer Benachrichtigung


Galileo Computing - Zum Seitenanfang

5.7.3 Das Delegate des FetchedresultscontrollersZur nächsten ÜberschriftZur vorigen Überschrift

Der Fetchedresultscontroller wiederum muss die Änderungen an den Tableviewcontroller weitergeben. Der Viewcontroller ist deswegen das Delegate des Fetchedresultscontrollers und implementiert dessen Delegate-Protokoll NSFetchedResultsControllerDelegate. Die Delegate-Methoden informieren direkt den Tableview über die Datenänderungen. Abbildung 5.33 stellt den Datenfluss schematisch dar.

Die Datenaktualisierung läuft wie folgt ab:

  1. Ein beliebiger Objektkontext speichert seine Daten. Dabei ruft er über eine DidSave-Benachrichtigung an das Notificationcenter [Das Notificationcenter ist in der Abbildung nicht dargestellt.] die Methode managedObjectContextDidSave: des Viewcontrollers auf.
  2. Die Methode aktualisiert den Objektkontext des Viewcontrollers, indem sie die Änderungen aus der Benachrichtigung in den Kontext einfließen lässt.
  3. Dieser Objektkontext benachrichtigt den Fetchedresultscontroller. Das geschieht automatisch, und Sie brauchen dafür nichts zu programmieren.
  4. Der Fetchedresultscontroller ruft seine Delegate-Methoden auf. Dadurch leitet er die Änderungen wieder an den Viewcontroller weiter.
  5. Die Delegate-Methoden aktualisieren den Tableview.

    Abbildung

    Abbildung 5.33 Datenaktualisierung im Fototagebuch

Der ganze Ablauf ist recht komplex und wirkt unnötig kompliziert. Er hat allerdings auch gravierende Vorteile. Die Objektkontexte, die den Aktualisierungsvorgang auslösen, sind vollkommen unabhängig von dem Viewcontroller. Sie können also zu der App beliebige weitere Objektkontexte hinzufügen, ohne die Logik des Photodiaryviewcontrollers ändern zu müssen. Jedes Mal, wenn einer dieser Objektkontexte seine Elemente speichert, aktualisiert er auch automatisch den Tableview.

Ein weiterer Vorteil entsteht durch die Nutzung des Fetchedresultscontrollers, der Ihnen eine relativ einfache Implementierung der Datenquelle erlaubt. Außerdem wertet er die Änderungen an den Core-Data-Objekten aus und berechnet die Indexpfade für die Aktualisierung des Tableviews.

Diese Aktualisierungen erfolgen über vier Delegate-Methoden des Fetchedresultscontrollers. Der Controller klammert alle zusammenhängenden Änderungen zwischen Aufrufe der Delegate-Methoden controllerWillChangeContent: und controllerDidChangeContent:. Über diese Methoden können Sie den Tableview über bevorstehende Änderungen und deren Abschluss informieren (siehe Listing 5.64). Durch diese Klammerung führt der Tableview die dazwischenliegenden Aktualisierungsoperationen simultan in einem Block aus.

- (void)controllerWillChangeContent:Zeilenumbruch
(NSFetchedResultsController *)inController {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:Zeilenumbruch
(NSFetchedResultsController *)inController {
[self.tableView endUpdates];
}

Listing 5.64 Beginn und Ende der Aktualisierung des Tableviews

Für die Veränderung jedes Objekts im Objektkontext ruft der Fetchedresultscontroller jeweils einmal die Delegate-Methode controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: auf. Dabei enthält der zweite Parameter das geänderte Objekt. Der dritte und der fünfte Parameter enthalten die Indexpfade mit der bisherigen beziehungsweise der neuen Position des Objekts in der Tabelle. Der Wert des vierten Parameters beschreibt die Änderungsart:

Tabelle 5.6 Aktualisierungsarten des Fetchedresultscontrollers

Änderungsart Beschreibung

NSFetchedResultsChangeInsert

Das Objekt wurde an der Position des neuen Indexpfades eingefügt.

NSFetchedResultsChangeDelete

Das Objekt an der Position des bisherigen Indexpfades wurde gelöscht.

NSFetchedResultsChangeMove

Das Objekt wurde von der Position des
bisherigen zu der Position des neuen
Indexpfades verschoben.

NSFetchedResultsChangeUpdate

Die Attribute des Objekts an der Position des bisherigen Indexpfades wurden
aktualisiert.

Diese Änderungen setzt der Photodiaryviewcontroller über eine Fallunterscheidung anhand des Typs in Aktualisierungsoperationen auf dem Tableview um.

- (void)controller:(NSFetchedResultsController *)inControllerZeilenumbruch
didChangeObject:(id)inObjectZeilenumbruch
atIndexPath:(NSIndexPath *)inIndexPathZeilenumbruch
forChangeType:(NSFetchedResultsChangeType)inTypeZeilenumbruch
newIndexPath:(NSIndexPath *)inNewIndexPath {
id theCell;
switch(inType) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:Zeilenumbruch
[NSArray arrayWithObject:inNewIndexPath]Zeilenumbruch
withRowAnimation:UITableViewRowAnimationRight];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:Zeilenumbruch
[NSArray arrayWithObject:inIndexPath]Zeilenumbruch
withRowAnimation:UITableViewRowAnimationRight];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:Zeilenumbruch
[NSArray arrayWithObject:inIndexPath]Zeilenumbruch
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:Zeilenumbruch
[NSArray arrayWithObject:inNewIndexPath]Zeilenumbruch
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
theCell = [self.tableViewZeilenumbruch
cellForRowAtIndexPath:inIndexPath];
[self applyDiaryEntry:inObject toCell:theCell];
break;
}
}

Listing 5.65 Aktualisierung des Tableviews

Während Sie das Einfügen und Löschen durch die jeweils analogen Methoden insertRowsAtIndexPaths:withRowAnimation: beziehungsweise deleteRowsAtIndexPaths:withRowAnimation: umsetzen können, müssen Sie das Verschieben durch ein Löschen und ein Einfügen umsetzen. Das Fototagebuch benötigt diese Änderungsart zwar nicht, weil es die Einträge immer in der Erzeugungsreihenfolge anzeigt. Wenn Sie jedoch ein anderes Sortierkriterium verwenden oder die Erzeugungszeit editierbar machen, müssen Sie auch diesen Fall implementieren.

Für den Änderungstyp NSFetchedResultsChangeUpdate müssen Sie nicht den Tableview, sondern den Inhalt einer Zelle verändern. Sie können sich dazu die Zelle über die Methode cellForRowAtIndexPath: des Tableviews holen. Da der Viewcontroller die bereits beschriebene Methode applyDiaryEntry:toCell: (siehe Listing 5.51) für die Aktualisierung der Zellen verwendet, können Sie diese Methode hier auch aufrufen.

Wenn Sie Ihren Tableview in Abschnitte unterteilt haben, sollten Sie auch die Methode controller:didChangeSection:atIndex:forChangeType: implementieren. Sie ist das Gegenstück zu der gerade besprochenen Delegate-Methode für Objekte. Der Parameter für den Änderungstyp kann hier nur die Werte für Einfügen oder Löschen annehmen. Die Implementierung dieser beiden Fälle erfolgt analog über die Methoden insertSections:withRowAnimation: beziehungsweise deleteSections:withRowAnimation: des Tableviews.

Tipp

Die Implementierung des Fetchedresultscontroller-Delegates ist in den meisten Fällen sehr ähnlich. Sie können dafür in der Regel den Code des Photodiaryviewcontrollers als Ausgangsbasis verwenden.


Galileo Computing - Zum Seitenanfang

5.7.4 Tabelleneinträge suchenZur vorigen Überschrift

Der Tableview zeigt immer das komplette Tagebuch an. Wenn Sie Ihr Tagebuch ständig erweitern, haben Sie schnell sehr viele Einträge darin. Dann kann es für Sie schwierig werden, bestimmte Einträge wiederzufinden. Bei Tableviews mit vielen Einträgen sollten Sie dem Nutzer eine Möglichkeit geben, gezielt nach Einträgen zu suchen.

Dafür stellt Cocoa Touch sogar einen eigenen View samt Controller zur Verfügung. Sie können im Interface Builder einen View der Klasse UISearchBar direkt auf einen Tableview ziehen. Wenn Sie allerdings den View mit einem Searchbarcontroller verwenden möchten, können Sie auch direkt beide Komponenten in einem Schritt anlegen. Ziehen Sie dazu das Element Search Bar with Search Display Controller auf den Tableview (siehe Abbildung 5.34 rechts). Bei dieser Aktion fügt der Interface Builder den View in den Tableview ein und legt einen Controller der Klasse UISearchDisplayController an. Sie brauchen dann nur noch eine Outlet-Property anzulegen, über die der Photodiaryviewcontroller den Searchdisplaycontroller hält. Außerdem sollte der Viewcontroller als Delegate des Searchdisplaycontrollers dienen.

Abbildung

Abbildung 5.34 Searchbar und Searchbar mit Controller

Dieser Controller besitzt einen eigenen Tableview für die Anzeige des Suchergebnisses, der die gleiche Datenquelle und das gleiche Delegate wie der Tableview des Viewcontrollers verwendet. Aus diesem Grund müssen Sie an einigen Stellen unterscheiden, für welchen Tableview die Methode der Datenquelle beziehungsweise des Delegates aufgerufen wurde. Der Viewcontroller macht das über einen Vergleich mit seiner Property tableView. Die Methode entryForTableView:atIndexPath: sieht beispielsweise folgendermaßen aus:

- (DiaryEntry *)entryForTableView:(UITableView *)inTableViewZeilenumbruch
atIndexPath:(NSIndexPath *)inIndexPath {
if(inTableView == self.tableView) {
return [self.fetchedResultsControllerZeilenumbruch
objectAtIndexPath:inIndexPath];
}
else {
return [self.searchResultZeilenumbruch
objectAtIndex:inIndexPath.row];
}
}

Listing 5.66 Bestimmung des Tagebucheintrags zu einem Indexpfad

Das Suchergebnis speichert der Viewcontroller in der Property searchResult. Dieses Array setzt die Delegate-Methode searchDisplayController:shouldReloadTableForSearchString:, die für die Berechnung des Suchergebnisses vorgesehen ist. Die Methode liefert einen booleschen Wert zurück, über den Sie das Neuladen des Tableviews für die Suche steuern können.

- (BOOL)searchDisplayController:Zeilenumbruch
(UISearchDisplayController *)inControllerZeilenumbruch
shouldReloadTableForSearchString:(NSString *)inValue {
NSFetchRequest *theRequest = self.fetchRequest;
NSPredicate *thePredicate = Zeilenumbruch
[NSPredicatepredicateWithFormat:Zeilenumbruch
@"text contains[cd] %@", inSearchString];

theRequest.predicate = thePredicate;
theRequest.fetchLimit = 30;
self.searchResult = [self.managedObjectContextZeilenumbruch
executeFetchRequest:theRequest error:NULL];
return YES;
}

Listing 5.67 Berechnung des Suchergebnisses

Für die Berechnung des Suchergebnisses verwendet die Methode ein Prädikat, mit dem sie eine Suche über die Methode executeFetchRequest:error: durchführt. Der Searchdisplaycontroller ruft die Delegate-Methode auf, sobald der Nutzer den ersten Buchstaben eingegeben hat. Bei kurzen Eingaben kann es natürlich passieren, dass das Prädikat auf alle Tagebucheinträge passt. Sie sollten also Vorkehrungen treffen, dass der Fetchrequest in diesem Fall nicht alle Einträge lädt und die App wegen Speichermangels abstürzt. Aus diesem Grund begrenzt die Delegate-Methode die maximale Anzahl der Ergebniselemente über den Setter fetchLimit auf 30.

Der Tableview des Searchdisplaycontrollers besitzt die gleichen Eigenschaften wie der des Viewcontrollers. Sie können über die Suche auch Einträge löschen. Sie müssen also beim Löschen den Eintrag auch aus dem Suchergebnis löschen. Dazu müssen Sie den Code aus Listing 5.56 anpassen:

- (UITableView *)searchResultsTableView {
return self.searchDisplayController.searchResultsTableView;
}
- (void)tableView:(UITableView *)inTableViewZeilenumbruch
commitEditingStyle:(UITableViewCellEditingStyle)inStyleZeilenumbruch
forRowAtIndexPath:(NSIndexPath *)inIndexPath {
if(inStyle == UITableViewCellEditingStyleDelete) {
DiaryEntry *theItem = [self entryForTableView:Zeilenumbruch
inTableView atIndexPath:inIndexPath];
NSError *theError = nil;

[self.managedObjectContext rollback];
[self.managedObjectContext deleteObject:theItem];
if([self.managedObjectContext save:&theError]) {
if(inTableView == self.searchResultsTableView) {
NSMutableArray *theResult =Zeilenumbruch
[self.searchResult mutableCopy];

[theResult
removeObjectAtIndex:inIndexPath.row];
self.searchResult = theResult;
[inTableView deleteRowsAtIndexPaths:Zeilenumbruch
[NSArray arrayWithObject:inIndexPath]Zeilenumbruch
withRowAnimation:Zeilenumbruch
UITableViewRowAnimationFade];
}
}
else {
NSLog(@"Unresolved error %@", theError);
}
}
}

Listing 5.68 Löschen eines Eintrags aus dem Suchergebnis

In Listing 5.68 sind die neuen und geänderten Zeilen hervorgehoben. Die geänderte Methode erzeugt eine Kopie des Suchergebnisses über die Methode mutableCopy. Die damit erzeugte Kopie hat die Klasse NSMutableArray. Wie der Name schon andeutet, können Sie Arrays dieser Klasse auch nach der Konstruktion verändern. Die Delegate-Methode entfernt das zu löschende Element aus dem Array und setzt dieses dann als neues Suchergebnis. Danach aktualisiert sie den Tableview für das Suchergebnis über die Methode deleteRowsAtIndexPaths:withRowAnimation:.

Hinweise zur Speicherverwaltung

Die Property searchResult hat den Speicherverwaltungstyp copy. Die Zuweisung erzeugt also erst eine Kopie des Arrays theResult, weil es ja die Klasse NSMutableArray hat. Die Property zeigt also immer auf ein unveränderliches Array.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.







<< zurück
  Zum Katalog
Zum Katalog: Apps programmieren für iPhone und iPad

Apps programmieren für iPhone und iPad
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Einstieg in Objective-C 2.0 und Cocoa





 Einstieg in
 Objective-C 2.0
 und Cocoa


Zum Katalog: Apps entwickeln für iPhone und iPad - Videotraining






 Apps entwickeln für
 iPhone und iPad -
 Videotraining


Zum Katalog: Apps mit HTML5 und CSS3






 Apps mit HTML5
 und CSS3


Zum Katalog: iPhone- und iPad-Apps entwickeln






 iPhone- und
 iPad-Apps entwickeln


Zum Katalog: Android 4






 Android 4


Zum Katalog: Android-Apps entwickeln - Videotraining






 Android-Apps
 entwickeln -
 Videotraining


Zum Katalog: Windows Store Apps mit XAML und C#






 Windows Store Apps
 mit XAML und C#


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de