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

Inhaltsverzeichnis
Geleitwort
Vorwort
1 Hello iPhone
2 Die Reise nach iOS
3 Sehen und anfassen
4 Alles unter Kontrolle
5 Daten, Tabellen und Controller
6 Models, Layer, Animationen
7 Programmieren, aber sicher
8 Datenserialisierung und Internetzugriff
9 Multimedia
10 Jahrmarkt der Nützlichkeiten
Stichwort

Jetzt 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
Rheinwerk Computing
1172 S., geb., mit DVD
49,90 Euro, ISBN 978-3-8362-2734-6
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 Lang lebe das Rotationsprinzip!
Pfeil 5.2.2 Anpassung des Layouts
Pfeil 5.3 Core Data
Pfeil 5.3.1 Datenmodellierung
Pfeil 5.3.2 Implementierung von Entitätstypen
Pfeil 5.3.3 Dokumentenordner und Application-Support-Verzeichnis
Pfeil 5.3.4 Einbindung von Core Data
Pfeil 5.3.5 Der Objektkontext
Pfeil 5.3.6 Die Nachrichten des Objektkontexts
Pfeil 5.3.7 Anlegen und Ändern von Entitäten in der Praxis
Pfeil 5.4 Texte, Bilder und Töne verwalten
Pfeil 5.4.1 Viewanpassungen für die Systemleisten
Pfeil 5.4.2 Die Tastatur betritt die Bühne
Pfeil 5.4.3 Fotos aufnehmen
Pfeil 5.4.4 Töne aufnehmen und abspielen
Pfeil 5.5 Tableviews und Core Data
Pfeil 5.5.1 Tableviews
Pfeil 5.5.2 Tabellenzellen gestalten
Pfeil 5.5.3 Zellprototypen über das Storyboard definieren
Pfeil 5.5.4 Zellprototypen per Programmcode bereitstellen
Pfeil 5.5.5 Der Target-Action-Mechanismus und Tabellenzellen
Pfeil 5.5.6 Zellen löschen
Pfeil 5.6 Core Data II: Die Rückkehr der Objekte
Pfeil 5.6.1 Prädikate
Pfeil 5.6.2 Aktualisierung des Tableviews
Pfeil 5.6.3 Das Delegate des Fetched-Results-Controllers
Pfeil 5.6.4 Tabelleneinträge suchen
Pfeil 5.7 Inhalte teilen
Pfeil 5.7.1 Integration in das Fototagebuch
Pfeil 5.7.2 Eigene Aktivitäten bereitstellen
Pfeil 5.8 Collectionviews
Pfeil 5.8.1 Der Collectionviewcontroller
Pfeil 5.8.2 Gitterdarstellung
Pfeil 5.8.3 Zellen und die Datenquelle
Pfeil 5.8.4 Ergänzende Views
Pfeil 5.8.5 Freie Layouts
Pfeil 5.8.6 Freie Layouts und ergänzende Views
Pfeil 5.8.7 Decorationviews

Rheinwerk Computing - Zum Seitenanfang

5.8CollectionviewsZur nächsten Überschrift

Während der Tableview zu den wichtigsten Views auf dem iPhone gehört, ist er für Fullscreenviews auf dem iPad in vielen Fällen ungeeignet. Die Zellen nehmen die komplette Bildschirmbreite ein, wodurch das Layout sehr klobig oder sehr leer wirkt. Eine Tabellenansicht mit mehreren Spalten oder eine zweidimensionale Verteilung der Zellen würde sich hier besser eignen.

Projektinformation

Den Quellcode des folgenden Beispiels finden Sie auf der DVD unter Code/Apps/iOS6/UniversalYouTube oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS6/UniversalYouTube.

Dieses Beispielprojekt lädt die Einträge über eine HTTP-Anfrage von YouTube und zeigt sie auf einem iPad oder dem iPad-Simulator in einem Collectionview an. Die für HTTP-Anfragen notwendigen Grundlagen sind für das Verständnis von Collectionviews nicht notwendig, weswegen wir erst in Kapitel 8, »Datenserialisierung und Internetzugriff«, genauer darauf eingehen.

Apple hat mit iOS 6 Collectionviews eingeführt, mit denen Sie analog zu Tableviews große Datenmengen ressourcenschonend anzeigen können. Dabei legt der Collectionview jedoch keine Anordnung der Zellen fest. Sie können die Zellen in einem Gitter anordnen oder auch frei auf der Fläche des Collectionviews platzieren. Dabei ist es wie bei Tableviews möglich, die Zellen in Abschnitte zu gruppieren, und jeder Abschnitt kann ergänzende Views anzeigen.

Abbildung

Abbildung 5.29 Aufbau eines Collectionviews

Analog zum Tableview besitzt jeder Collectionview auch eine Datenquelle zum Erzeugen und Konfigurieren von Zellen sowie ein Delegate. Diese beiden Aufgaben übernimmt in der Regel der Viewcontroller, der den Collectionview verwaltet. Apple stellt hierfür die Basisklasse UICollectionViewController bereit, deren Instanzen sich bequem über den Interface Builder konfigurieren lassen. Außerdem besitzt ein Collectionview ein Layoutobjekt, das für die Anordnung der Zellen und der ergänzenden Views im Collectionview zuständig ist. Zusätzlich kann es Decorationviews in den Abschnitten frei platzieren. Abbildung 5.29 stellt die Komponenten eines Collectionviews bis auf die Decorationviews grafisch dar.


Rheinwerk Computing - Zum Seitenanfang

5.8.1Der CollectionviewcontrollerZur nächsten ÜberschriftZur vorigen Überschrift

Wenn Sie eine Szene mit einem Collectionview im Interface Builder erstellen möchten, sollten Sie für den Viewcontroller die Basisklasse UICollectionViewController verwenden. Sie erzeugen ihn, indem Sie aus der Bibliothek das entsprechende Symbol – das ist das ausgewählte Icon unten rechts in Abbildung 5.30 – auf die Zeichenfläche ziehen. Im Attributinspektor eines Collectionviewcontrollers können Sie das Layoutobjekt vorkonfigurieren. Dazu haben Sie zwei Optionen:

  1. Das Flowlayout ordnet die Zellen in einem Gitter an und unterstützt die Anzeige von Headern und Footern in den Abschnitten. Sie können diese ergänzenden Views über den Attributinspektor des Collectionviews einschalten, der sie als konfigurierbare Subviews anzeigt (siehe Abbildung 5.30). Dieser Layouttyp hat die Klasse UICollectionViewFlowLayout, deren Instanzen Sie ebenfalls über den Interface Builder und über ein eigenes Delegate konfigurieren können.
  2. Ein Customlayout müssen Sie hingegen selbst implementieren. Hierfür können Sie im Attributinspektor des Controllers nur dessen Klasse festlegen.

Der Interface Builder zeigt das Layout als orangefarbenen Würfel in der Baumdarstellung an, wie Sie ihn oben links in Abbildung 5.30 sehen.


Rheinwerk Computing - Zum Seitenanfang

5.8.2GitterdarstellungZur nächsten ÜberschriftZur vorigen Überschrift

Das Flowlayout ist die einfachste Möglichkeit für die Anordnung der Zellen. Wenn Sie das Layoutobjekt in der Baumansicht auswählen, können Sie in seinem Attributinspektor nur die Scrollrichtung – horizontal oder vertikal – festlegen. Interessanter ist da der Größeninspektor, der Ihnen eine Konfiguration des Gitters erlaubt (siehe Abbildung 5.31).

Abbildung

Abbildung 5.30 Konfiguration eines Collectionviews in Xcode

Abbildung

Abbildung 5.31 Größeneinstellungen für das Flowlayout

In diesem Dialog können Sie die Größen für die Zellen, den Header und den Footer bestimmen. Die beiden Werte unter Min Spacing legen den minimalen horizontalen Abstand zwischen den Zellen (minimumInteritemSpacing) in einer Zeile und den minimalen Abstand zwischen den Zeilen (minimumLineSpacing) fest. Außerdem können Sie über die Section Insets den Innenabstand der Zellen zum Header und Footer sowie zu den Seiten festlegen. Eine Darstellung dieser Größen sehen Sie in Abbildung 5.32.

Abbildung

Abbildung 5.32 Die Abstände des Flowlayouts

Verdrehte Welt

Die Beschreibungen für das Flowlayout gelten für das vertikale Scrollen. Wenn Sie stattdessen horizontales Scrollen verwenden, stellt der Collectionview den Header auf der linken und den Footer auf der rechten Seite dar. Außerdem legt minimumInteritemSpacing den minimalen vertikalen Abstand der Zellen in einer Spalte fest und minimumLineSpacing den minimalen Abstand der Spalten.

Sie können im Delegate des Collectionviews auch Delegate-Methoden für das Flowlayout implementieren, die das Protokoll UICollectionViewDelegateFlowLayout deklariert. Das Flowlayout verwendet also immer das gleiche Delegateobjekt wie der Collectionview.

Das Delegate ermöglicht es Ihnen, diese Größen dynamisch an die Zellen und Abschnitte anzupassen. Das Beispielprojekt verwendet beispielsweise für jede Zelle genau die Größe, die sie zur Darstellung des kompletten Textinhalts benötigt. Dadurch haben alle Zellen eine individuelle Größe. Die Berechnung geschieht dabei in der Delegate-Methode collectionView:layout:sizeForItemAtIndexPath:, die jeweils die optimale Größe einer Zelle über eine Hilfszelle berechnet. Sie erzeugt dazu eine Zelle, übergibt ihr den anzuzeigenden Text und berechnet über sizeThatFits: die Größe.

- (CGSize)collectionView:(UICollectionView *)inCollectionView 
layout:(UICollectionViewLayout *)inCollectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)inIndexPath {
YouTubeCell *theCell =
[[YouTubeCell alloc] initWithFrame:CGRectNull];
NSDictionary *theItem =
[self.items objectAtIndex:inIndexPath.row];
CGSize theSize = CGSizeMake(244.0, MAXFLOAT);

theCell.text = [theItem objectForKey:@"text"];
return CGSizeMake(244.0,
[theCell sizeThatFits:theSize].height);
}

Listing 5.86 Dynamische Zellengrößen über das Delegate

Die Klasse YouTubeCell überschreibt die Methode sizeThatFits:, um die benötigte Größe der Zelle berechnen zu können. Der View der Zelle enthält ein Label für den Titel und einen Text-View für die Nachricht (siehe Abbildung 5.33), und so berechnet die Implementierung die Größe des Text-Views und rechnet die Höhe des Labels hinzu.

Abbildung

Abbildung 5.33 Zellen mit dynamischer Größe im Collectionview

Listing 5.86 enthält den Code der Methode sizeThatFits:. Dabei gibt der Größenwert im Parameter die maximale Breite und Höhe der Zelle an; also darf der Text-View höchstens so hoch sein wie die Differenz dieser Höhe und der Höhe des Titels. Nach der Berechnung der Größe des Text-Views muss die Methode die Höhe des Labels jedoch wieder dazurechnen, um die Gesamthöhe der Zelle zu bestimmen. Als Breite der Zelle verwendet die Methode immer die Breite des Parameterwertes.

- (CGSize)sizeThatFits:(CGSize)inSize {
CGSize theSize = inSize;

theSize.height -= kCellTitleHeight;
theSize = [self.textView sizeThatFits:theSize];
theSize.height += kCellTitleHeight;
return theSize;
}

Listing 5.87 Berechnung der Zellgröße


Rheinwerk Computing - Zum Seitenanfang

5.8.3Zellen und die DatenquelleZur nächsten ÜberschriftZur vorigen Überschrift

Collectionviews besitzen einen ähnlichen Mechanismus zur Wiederverwendung von Zellen wie Tableviews. Allerdings müssen Sie bei Collectionviews immer Prototypen verwenden und legen die Zellen somit immer über die Dequeue-Methode an. Für die Definition der Zellprototypen haben Sie wie beim Tableview drei Möglichkeiten:

  1. Sie können im Interface Builder eine Zelle auf den Collectionview ziehen, sie dort konfigurieren und mit Subviews befüllen.
  2. In einer XIB-Datei können Sie jeweils einen Zellprototyp anlegen und über die Methode registerNib:forCellWithReuseIdentifier: an den Collectionview übergeben. Das NIB laden Sie dabei analog zu Listing 5.54.
  3. Außerdem können Sie Klassen über die Methode registerClass:forCellWithReuseIdentifier: beim Collectionview registrieren, der damit die Zellen über den Initializer initWithFrame: erzeugt.

Collectionviews besitzen nahezu den gleichen Mechanismus wie Tableviews für die Erzeugung der Zellen: Eine Datenquelle implementiert die drei Methoden numberOfSectionsInCollectionView:, collectionView:numberOfItemsInSection: und collectionView:cellForItemAtIndexPath:, über die der Collectionview die benötigten Zellen abfragt. Die Implementierung der Methode collectionView:cellForItemAtIndexPath: sieht im Beispielprojekt so aus:

- (UICollectionViewCell *)collectionView: 
(UICollectionView *)inCollectionView
cellForItemAtIndexPath:(NSIndexPath *)inIndexPath {
YouTubeCell *theCell = [inCollectionView
dequeueReusableCellWithReuseIdentifier:@"YouTube"
forIndexPath:inIndexPath];
NSDictionary *theItem =
[self.items objectAtIndex:inIndexPath.row];
float theRating = [[theItem
valueForKeyPath:@"gd$rating.average"] floatValue];
UIColor *theColor;

if(theRating < 1) {
theColor = [UIColor darkGrayColor];
}
else {
float theValue = (theRating – 1.0) / 4.0;

theColor = [UIColor colorWithRed:1.0 – theValue
green:theValue blue:0.0 alpha:1.0];
}
theCell.title = [theItem valueForKeyPath:@"title.$t"];
theCell.text = [theItem valueForKeyPath:@"content.$t"];
theCell.titleColor = theColor;
return theCell;
}

Listing 5.88 Erzeugung von Zellen für einen Collectionview

In der Methode aus Listing 5.88 spielt der Aufruf der Methode dequeueReusableCellWithReuseIdentifier:forIndexPath: eine zentrale Rolle: Entweder erzeugt sie eine neue Zelle aus dem Prototyp mit der angegebenen Kennung, oder sie liefert eine bereits erzeugte, allerdings momentan nicht angezeigte Zelle, die zu der Kennung passt. Falls es keinen Zellprototyp gibt, der zu der angegebenen Kennung passt, löst diese Methode eine Ausnahme des Typs NSInternalInconsistencyException mit der Nachricht

could not dequeue a view of kind: UICollectionElementKindCell with identifier 
XXX – must register a nib or a class for the identifier or connect a
prototype cell in a storyboard

aus, wobei XXX für die verwendete Kennung steht. Den Indexpfad im zweiten Parameter verwendet der Collectionview, um die Zelle entsprechend zum Layout zu konfigurieren.

Die Methode der Datenquelle übergibt der Zelle den Titel und den anzuzeigenden Text. Außerdem setzt sie die Hintergrundfarbe des Titels in Abhängigkeit von der Bewertung des Videos. Die Anzahl der Abschnitte und die Zellenzahl pro Abschnitt stellen Sie dem Collectionview analog wie einem Tableview zur Verfügung, wie Sie in Listing 5.89 sehen:

- (NSInteger)numberOfSectionsInCollectionView: 
(UICollectionView *)inCollectionView {
return 1;
}

- (NSInteger)collectionView:
(UICollectionView *)inCollectionView
numberOfItemsInSection:(NSInteger)inSection {
return self.items.count;
}

Listing 5.89 Anzahl der Abschnitte und Zellen bereitstellen


Rheinwerk Computing - Zum Seitenanfang

5.8.4Ergänzende ViewsZur nächsten ÜberschriftZur vorigen Überschrift

Die Datenquelle stellt neben den Zellen die ergänzenden Views für den Collectionview bereit. Auch für diese Views stellt der Collectionview einen Mechanismus für die Wiederverwendung zur Verfügung, der denen für die Zellen ähnelt. Allerdings legt nicht die Datenquelle die Anzahl der ergänzenden Views fest, sondern das Layout. Darauf gehen wir im nächsten Abschnitt noch genauer ein.

Die Datenquelle stellt die Views über die Methode collectionView:viewForSupplementaryElementOfKind:atIndexPath: bereit. Sie müssen die (Ober-)Klasse UICollectionReusableView haben, die ebenfalls die Oberklasse von UICollectionViewCell ist. Der Collectionview unterscheidet die ergänzenden Views nach ihrer Art, die der Parameter kind enthält. Dabei hängen die unterstützten Arten vom Layout ab. Das Flowlayout unterstützt zurzeit die beiden Arten UICollectionElementKindSectionHeader und UICollectionElementKindSectionFooter für die Abschnittsheader und -footer.

Für die ergänzenden Views stellt der Collectionview einen analogen Warteschlangenmechanismus wie für die Zellen bereit. Sie müssen also zuerst über die Methode registerNib:forSupplementaryViewOfKind:withReuseIdentifier: oder registerClass:forSupplementaryViewOfKind:withReuseIdentifier: NIBs oder Klassen für die ergänzenden Views registrieren, auf die Sie dann in der Methode der Datenquelle über dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: zugreifen können. Wenn Sie ein Flowlayout verwenden, können Sie die ergänzenden Views für den Header und den Footer auch im Interface Builder anlegen und konfigurieren. Die Registrierung über eine der oben genannten Methoden entfällt dann.

Das Beispielprojekt zeigt im Header eine Suchleiste und im Footer einen Reload-Button. Die Methode der Datenquelle muss den Header jedoch nicht nur laden, sondern auch konfigurieren; sie setzt das Delegate und den Suchbegriff der Suchleiste. Listing 5.90 enthält den Code dieser Methode:

- (UICollectionReusableView *)collectionView: 
(UICollectionView *)inCollectionView
viewForSupplementaryElementOfKind:(NSString *)inKind
atIndexPath:(NSIndexPath *)inIndexPath {
UICollectionReusableView *theView = nil;

if([UICollectionElementKindSectionHeader
isEqualToString:inKind]) {
theView = [inCollectionView
dequeueReusableSupplementaryViewOfKind:inKind
withReuseIdentifier:@"Searchbar"
forIndexPath:inIndexPath];
UISearchBar *theSearchBar = (UISearchBar *)
[theView viewWithTag:10];

theSearchBar.text = self.query;
theSearchBar.delegate = self;
}
else if([UICollectionElementKindSectionFooter
isEqualToString:inKind]) {
theView = [inCollectionView
dequeueReusableSupplementaryViewOfKind:inKind
withReuseIdentifier:@"Toolbar"
forIndexPath:inIndexPath];
}
return theView;
}

Listing 5.90 Erzeugung der ergänzenden Views in der Datenquelle

Bei Flowlayouts reicht die Konfiguration des Headers und des Footers im Interface Builder sowie die Implementierung der Methode collectionView:viewForSupplementaryElementOfKind:atIndexPath: aus, um diese beiden Arten von ergänzenden Views anzuzeigen.


Rheinwerk Computing - Zum Seitenanfang

5.8.5Freie LayoutsZur nächsten ÜberschriftZur vorigen Überschrift

Wenn der Collectionview die Zellen nicht in einem Gitter anordnen oder eigene ergänzende Views beziehungsweise Decorationviews anzeigen soll, müssen Sie ein eigenes Layout implementieren. Die Funktionsweise des Layouts ist dabei relativ einfach: Es erzeugt für jedes anzuzeigende Element ein Attributobjekt der Klasse UICollectionViewLayoutAttributes, das die Darstellungsinformationen für den entsprechenden View enthält. Dazu gehören die Position und Größe des Views relativ zum Collectionview, die Sie entweder über die Property frame oder die Propertys center und size im Attributobjekt setzen können. Wenn sich die Zellen im Collectionview überlappen können, ist die Property zIndex wichtig, die die Tiefenanordnung der Zellen bestimmt. Außerdem können Sie über die Propertys alpha und hidden die Sichtbarkeit der Zelle beeinflussen und über transform3D geometrische Transformationen ausführen.

Projektinformation

Sie können das Layout, das dieser Abschnitt behandelt, in Aktion erleben, indem Sie im Storyboard MainStoryboard-iPad.storyboard den initialen Viewcontroller wechseln. Dazu öffnen Sie im Beispielprojekt UniversalYouTube den Attributinspektor des Viewcontrollers in der unteren Szene des Storyboards und setzen den Haken bei Is Initial View Controller.

Freie Layouts basieren auf der Klasse UICollectionViewFlowLayout oder deren Oberklasse UICollectionViewLayout, wobei sich das Flowlayout als Basis bei gitterartiger Anordnung der Zellen empfiehlt. Das Beispielprojekt enthält ein freies Layout mit der Klasse StackLayout, das die Zellen in einem schräg ausgebreiteten Kartenstapel anzeigt, wobei der älteste Eintrag oben links und der aktuellste unten rechts erscheint. Abbildung 5.34 zeigt die Beispielapplikation mit eingeschaltetem Stacklayout.

Abbildung

Abbildung 5.34 Das Stacklayout in Aktion

Im Interface Builder aktivieren Sie ein freies Layout, indem Sie im Attributinspektor des Collectionviews in der Rubrik Collection View unter Layout den Eintrag Custom auswählen. Außerdem müssen Sie die Klasse des Layouts unter Class eintragen (siehe Abbildung 5.35).

Abbildung

Abbildung 5.35 Auswahl des Layouts im Attributinspektor

Kenne mer nit, bruche mer nit, fott domet [Anm.: Wörtlich: »Kennen wir nicht, brauchen wir nicht, fort damit«]

Wenn Sie vom Flowlayout auf ein Customlayout umschalten, löscht der Interface Builder eventuell vorhandene Header- und Footerviews. Zurzeit unterstützen Storyboards bei freien Layouts noch nicht das Anlegen von ergänzenden Views im Collectionview. Sie können diese Views dann nur über die Undo-Funktion wiederherstellen, wenn Sie sie nicht anderweitig gespeichert haben.

In der Baumdarstellung zeigt der Interface Builder auch einen Würfel für das freie Layout an (siehe Abbildung 5.36).

Abbildung

Abbildung 5.36 Freies Layout im Collectionview

Eigene Layoutklassen müssen mindestens die drei Methoden collectionViewContentSize, layoutAttributesForItemAtIndexPath: und layoutAttributesForElementsInRect: überschreiben. Dabei liefert die erste Methode die Größe, die alle Zellen und Views des Collectionviews zusammen belegen. Das Stacklayout des Beispielprojekts gibt hierfür einfach die Größe des Views zurück:

- (CGSize)collectionViewContentSize {
return self.collectionView.bounds.size;
}

Listing 5.91 Einfache Berechnung der Inhaltsgröße

Natürlich ist die Berechnung nicht für alle Layouts so einfach. Sie können jedoch die Berechnung der Inhaltsgröße auf die Layoutattribute zurückführen. Dazu brauchen Sie nur das kleinste Rechteck zu berechnen, in das alle Zellen und Views hineinpassen. Dabei hilft Ihnen die Core-Graphics-Function CGRectUnion, mit der Sie sukzessive dieses Rechteck bestimmen können.

Das Stacklayout berechnet alle Layoutattribute in der Methode prepareLayout und speichert diese Werte in der privaten Property attributes. Das Layout ruft diese Methode immer auf, wenn der Collectionview das Layout für ungültig erklärt hat. Listing 5.92 gibt die Rumpfmethode für die Berechnung der Layoutattribute und der Inhaltsgröße wieder.

- (void)prepareLayout { 
NSInteger theSectionCount =
[self.collectionView numberOfSections];
NSMutableArray *theArray = [NSMutableArray array];
UICollectionViewLayoutAttributes *theAttributes;
CGRect theFrame = CGRectZero;

for(NSInteger i = 0; i < theSectionCount; ++i) {
NSInteger theCount =
[self.collectionView numberOfItemsInSection:i];

// (1)
for(NSInteger j = 0; j < theCount; ++j) {
NSIndexPath *theIndexPath =
[NSIndexPath indexPathForRow:j inSection:i];

theAttributes = ...; // (2)
[theArray addObject:theAttributes];
theFrame =
CGRectUnion(theAttributes.frame, theFrame);
}
// (3)
}
self.attributes = theArray;
self.contentSize = theFrame.size;
}

Listing 5.92 Rumpf für die Vorberechnung des Layouts

Im Listing sehen Sie, dass sich das Rechteck für die Inhaltsgröße aus der Vereinigung der Rechtecke aller Attribute berechnet. Komplexere Layouts können den so berechneten Wert der Property contentSize als Rückgabewert für collectionViewContentSize verwenden. Die Berechnung der Attribute erfolgt an den Stellen der Kommentare mit den Nummern. Die Anweisungen dafür folgen in Abschnitt 5.8.6.

Auf Basis der Property attributes lässt sich die Methode layoutAttributesForElementsInRect: implementieren. Sie liefert die Layoutattribute, deren Rahmen sich mit dem übergebenen Rechteck schneiden. Sie können diese Methode mit Hilfe eines Prädikats und der Funktion CGRectIntersectsRect relativ kurz formulieren. Dabei entscheidet die Funktion, ob sich die beiden übergebenen Rechtecke schneiden.

- (NSArray *)layoutAttributesForElementsInRect:
(CGRect)inRect {
NSPredicate *thePredicate =
[NSPredicate predicateWithBlock:
^BOOL(id inObject, NSDictionary *inBindings) {
return CGRectIntersectsRect(
inRect, [inObject frame]);
}];

return [self.attributes
filteredArrayUsingPredicate:thePredicate];
}

Listing 5.93 Filterung der Attribute zu einem Rechteck

Es fehlt jetzt nur noch die eigentliche Berechnung der Attribute für die Zellen und die Views; für die Zellen erfolgt das in der Methode layoutAttributesForItemAtIndexPath:.

Die Positionierung der Zellen erfolgt beim Stacklayout entlang einer Diagonalen von oben links nach unten rechts. Allerdings verläuft sie nicht von den Ecken des sichtbaren Bereiches, sondern durch ein gedachtes Rechteck innerhalb dieses Bereiches. Dabei ist die obere linke Ecke des Rechtecks der Mittelpunkt der Zelle mit dem ältesten Inhalt und die untere rechte Ecke die Zelle mit dem neuesten Inhalt. In Abbildung 5.37 sehen Sie eine schematische Darstellung der Anzeige, die dieses Rechteck als gepunktete Linie enthält. An der gestrichelten Linie richtet das Stacklayout die Zellen aus, wobei der Pfeil vom neuesten zum ältesten Eintrag verläuft.

Abbildung

Abbildung 5.37 Schematische Darstellung des Stacklayouts

Aus der Größe des sichtbaren Bereiches, der ersten und der letzten Zelle lässt sich dieses Rechteck berechnen. Im Beispielprogramm erledigt das die Methode cellFrameForSection:

- (CGRect)cellFrameForSection:(NSInteger)inSection {
UICollectionView *theView = self.collectionView;
NSInteger theCount =
[theView numberOfItemsInSection:inSection];
CGSize theSize = self.collectionView.bounds.size;
CGSize theFirstSize = [self sizeForItemAtIndexPath:
[NSIndexPath indexPathForRow:0 inSection:inSection]];
CGSize theLastSize = [self sizeForItemAtIndexPath:
[NSIndexPath indexPathForRow:theCount – 1
inSection:inSection]];
CGRect theFrame = CGRectZero;

theFrame.origin = CGPointMake(theLastSize.width / 2.0,
kHeaderHeight + theLastSize.height / 2.0);
theFrame.size = CGSizeMake(theSize.width –
theFirstSize.width / 2.0 – theLastSize.width / 2.0,

theSize.height – theFirstSize.height / 2.0 –
theLastSize.height / 2.0 – kHeaderHeight –
kFooterHeight);
return theFrame;
}

Listing 5.94 Berechnung des Rechtecks für einen Abschnitt

Der Code in Listing 5.94 berechnet die Position und die Größe des gepunkteten Rechtecks aus Abbildung 5.37. Zunächst errechnet die Methode in der Variablen theCount die Anzahl der Zellen des Abschnitts sowie die Größen des Collectionviews, der ersten und der letzten Zellen in den Variablen theSize, theFirstSize sowie theLastSize. Der Ursprungspunkt des Rechtecks ist nun einfach der Mittelpunkt der letzten [Anm.: Nein, nicht der ersten; die Reihenfolge der Zellen verläuft ja von unten rechts nach oben links.] Zelle, wobei Sie jedoch noch die Höhe des Headers hinzurechnen müssen. Die Breite berechnet sich aus der Breite des Collectionviews abzüglich der Abstände vom linken und rechten Rand, die jeweils der halben Zellbreite entsprechen. Analog verhält es sich mit der Höhe und den oberen und unteren Abständen, wobei Sie hier noch die Höhen des Headers und des Footers abziehen müssen. Abbildung 5.37 stellt auch die Größen für die Berechnung dieses Rechtecks dar.

Für die Berechnung der Größen der Zellen verwendet Listing 5.94 die Methode sizeForItemAtIndexPath:, die Sie in Listing 5.95 finden. Die Implementierung beruht auf der Methode collectionView:layout:sizeForItemAtIndexPath: aus Listing 5.86. Diese Methode implementiert zwar eine Delegate-Methode des Flowlayouts; es spricht aber nichts dagegen, sie auch für das Stacklayout zu verwenden:

- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)inIndexPath {
UICollectionView *theView = self.collectionView;
id theDelegate = theView.delegate;

return [theDelegate collectionView:theView layout:self
sizeForItemAtIndexPath:inIndexPath];
}

Listing 5.95 Berechnung der Zellgröße über das Collectionviewdelegate

Mit diesen beiden Hilfsmethoden cellFrameForSection: und sizeForItemAtIndexPath: können Sie nun die Methode layoutAttributesForItemAtIndexPath: implementieren. Dabei unterscheidet die Implementierung zwei Fälle: Wenn der Abschnitt nur eine Zelle enthält, positioniert die Methode sie genau in die Mitte des Rechtecks. Andernfalls platziert sie die Zelle entsprechend dem Verhältnis von ihrer Indexposition zur Indexposition der letzten Zelle.

- (UICollectionViewLayoutAttributes *) 
layoutAttributesForItemAtIndexPath:
(NSIndexPath *)inIndexPath {
UICollectionViewLayoutAttributes *theAttributes =
[UICollectionViewLayoutAttributes
layoutAttributesForCellWithIndexPath:inIndexPath];
UICollectionView *theView = self.collectionView;
NSInteger theCount =
[theView numberOfItemsInSection:inIndexPath.section];
CGRect theFrame =
[self cellFrameForSection:inIndexPath.section];
CGSize theSize = theFrame.size;
CGPoint thePoint;

if(theCount <= 1) {
thePoint.x = CGRectGetMidX(theFrame);
thePoint.y = CGRectGetMidY(theFrame);
}
else {
NSInteger theLastIndex = theCount – 1;
NSInteger theIndex = inIndexPath.row;

thePoint.x = CGRectGetMaxX(theFrame) –
theIndex * theSize.width / theLastIndex;
thePoint.y = CGRectGetMaxY(theFrame) –
theIndex * theSize.height / theLastIndex;
}
theAttributes.center = thePoint;
theAttributes.size =
[self sizeForItemAtIndexPath:inIndexPath];
theAttributes.zIndex = -inIndexPath.row;
return theAttributes;
}

Listing 5.96 Berechnung der Layoutattribute für eine Zelle

Damit die Einträge auch schön aufeinanderliegen – also Zellen mit neuerem Inhalt immer vor den Zellen mit dem älteren Inhalt liegen –, setzt die Methode auch den z-Index in den Layoutattributen. Sie verwendet dazu den negativen Indexwert, damit ältere Zellen mit höheren Indexwerten nicht neuere Zellen mit niedrigeren Indexwerten verdecken.

Sie können nun in Listing 5.92 die drei Punkte an Position (2) durch den Ausdruck [self layoutAttributesForItemAtIndexPath:theIndexPath] ersetzen. Damit erzeugt die Methode prepareLayout die Layoutattribute für alle Zellen.


Rheinwerk Computing - Zum Seitenanfang

5.8.6Freie Layouts und ergänzende ViewsZur nächsten ÜberschriftZur vorigen Überschrift

Im Gegensatz zum Flowlayout müssen Sie beim Stacklayout auch die Layoutattribute für die Abschnittsheader und -footer selbst erzeugen. Das geschieht in der Methode layoutAttributesForSupplementaryViewOfKind:atIndexPath:, die ein freies Layout überschreiben muss, wenn es ergänzende Views unterstützen soll. Das Stacklayout unterstützt wie das Flowlayout zwei ergänzende Views für Header und Footer. Die Implementierung verwendet dabei die gleichen Konstanten für die Art wie das Flowlayout.

Beliebige ergänzende Views

Auch wenn das Stacklayout nur Abschnittsheader und -footer bereitstellt, können Sie in Ihren Layouts beliebige Arten von eigenen ergänzenden Views zur Verfügung stellen. Sie sollten dafür möglichst eine eigene Zeichenkettenkonstante bereitstellen. Die Implementierung erfolgt ansonsten analog zum Header und Footer.

- (UICollectionViewLayoutAttributes *) 
layoutAttributesForSupplementaryViewOfKind:
(NSString *)inKind atIndexPath:(NSIndexPath *)inIndexPath {
UICollectionViewLayoutAttributes *theAttributes =
[UICollectionViewLayoutAttributes
layoutAttributesForSupplementaryViewOfKind:inKind
withIndexPath:inIndexPath];
CGSize theSize = [self collectionViewContentSize];

if([UICollectionElementKindSectionHeader
isEqualToString:inKind]) {
theAttributes.frame = CGRectMake(0.0, 0.0,
theSize.width, kHeaderHeight);
}
else if([UICollectionElementKindSectionFooter
isEqualToString:inKind]) {
theAttributes.frame =
CGRectMake(0.0, theSize.height – kFooterHeight,
theSize.width, kFooterHeight);
}
return theAttributes;
}

Listing 5.97 Layoutattribute für die ergänzenden Views im Stacklayout

Listing 5.97 stellt die Layoutattribute für den Header- und Footerview des Stacklayouts bereit. Damit der Collectionview diese Views auch anzeigt, müssen Sie in der Methode prepareLayout in Listing 5.92 an der Stelle (1) die Anweisungen

theAttributes = 
[self layoutAttributesForSupplementaryViewOfKind:
UICollectionElementKindSectionHeader atIndexPath:
[NSIndexPath indexPathForRow:0 inSection:i]];
[theArray addObject:theAttributes];
theFrame = CGRectUnion(theAttributes.frame, theFrame);

Listing 5.98 Einfügen der Layoutattribute für den Header ...

und an der Stelle (3) die Anweisungen

theAttributes = 
[self layoutAttributesForSupplementaryViewOfKind:
UICollectionElementKindSectionFooter atIndexPath:
[NSIndexPath indexPathForRow:1 inSection:i]];
[theArray addObject:theAttributes];
theFrame = CGRectUnion(theAttributes.frame, theFrame);

Listing 5.99 ... und den Footer im Stacklayout

einfügen.

Außerdem müssen Sie dem Collectionview mitteilen, wie er diese ergänzenden Views erzeugen kann. Dazu verfügt das Beispielprojekt über die Dateien Searchbar.xib und Toolbar.xib, die die Methode viewDidLoad der Klasse YouTubeCollectionViewController über die Methode registerNib:forSupplementaryViewOfKind:withReuseIdentifier: als NIB-Objekt an den Collectionview übergibt.

UINib *theNib = 
[UINib nibWithNibName:@"Searchbar" bundle:nil];

[theView registerNib:theNib forSupplementaryViewOfKind:
UICollectionElementKindSectionHeader
withReuseIdentifier:@"Searchbar"];
theNib = [UINib nibWithNibName:@"Toolbar" bundle:nil];
[theView registerNib:theNib forSupplementaryViewOfKind:
UICollectionElementKindSectionFooter
withReuseIdentifier:@"Toolbar"];

Listing 5.100 Registrierung der NIBs für Header und Footer

Bislang hat die Anordnung der Zellen des Stacklayouts noch einen Nachteil: Sie können den Inhalt der älteren Zellen nur sehr schlecht lesen. Aus diesem Grund können Sie einzelne Zellen durch Antippen in den Vordergrund holen. Dazu ändern Sie zunächst die vorletzte Anweisung in Listing 5.96 wie folgt:

theAttributes.zIndex = [theView.indexPathsForSelectedItems 
containsObject:inIndexPath] ? 0 : -inIndexPath.row;

Listing 5.101 Hervorhebung ausgewählter Zellen

Diese Änderung weist ausgewählten Zellen den z-Indexwert von 0 zu. Da die nicht ausgewählten Zellen ihren negativen Indexwert als z-Index haben, ist das der höchstmögliche Wert für das Stacklayout. Jetzt müssen Sie nur noch den Collectionview informieren, dass er die Zellen nach dem Antippen neu zeichnen soll. Dazu können Sie die Methode collectionView:didSelectItemAtIndexPath: verwenden. Über die Methode reloadItemsAtIndexPaths: weisen Sie in der Delegate-Methode den Collectionview an, die ausgewählte Zelle neu zu zeichnen.

- (void)collectionView:(UICollectionView *)inCollectionView 
didSelectItemAtIndexPath:(NSIndexPath *)inIndexPath {
[inCollectionView reloadItemsAtIndexPaths:@[inIndexPath]];
}

Listing 5.102 Aktualisierung ausgewählter Zellen

Analog soll der Collectionview ausgewählte Zellen wieder an ihrem ursprünglichen Platz einordnen, wenn der Nutzer sie deselektiert. Dieses Ereignis können Sie über die Delegatemethode collectionView:didDeselectItemAtIndexPath: abfangen.

- (void)collectionView:(UICollectionView *)inCollectionView 
didDeselectItemAtIndexPath:(NSIndexPath *)inIndexPath {
[inCollectionView reloadItemsAtIndexPaths:@[inIndexPath]];
}

Listing 5.103 Aktualisierung deselektierter Zellen


Rheinwerk Computing - Zum Seitenanfang

5.8.7DecorationviewsZur vorigen Überschrift

Das Stacklayout stellt außerdem einen Decorationview dar; das ist das YouTube-Logo in Abbildung 5.34. Im Gegensatz zu den anderen Views wird es nur über das Layout verwaltet. Sie müssen also die Klasse oder das NIB zur Erzeugung eines Decorationviews beim Layout und nicht beim Collectionview registrieren. Das Beispielprojekt verwendet für das YouTube-Logo eine eigene XIB-Datei, die ebenfalls die Methode viewDidLoad der Klasse YouTubeCollectionViewController lädt.

theNib = [UINib nibWithNibName:@"Logo" bundle:nil];
[theView.collectionViewLayout registerNib:theNib
forDecorationViewOfKind:@"Logo"];

Listing 5.104 Registrieren eines Decorationviews

Außerdem muss das Layout auch die Attribute über die Methode layoutAttributesForDecorationViewOfKind:decorationViewKind:atIndexPath: für die Decorationviews bereitstellen. Die Implementierung dieser Methode im Stacklayout verwendet für das Logo einfach eine feste Größe und Position.

- (UICollectionViewLayoutAttributes *) 
layoutAttributesForDecorationViewOfKind:(NSString *)inKind
atIndexPath:(NSIndexPath *)inIndexPath {
CGFloat theWidth =
CGRectGetWidth(self.collectionView.bounds);
UICollectionViewLayoutAttributes *theAttributes =
[UICollectionViewLayoutAttributes
layoutAttributesForDecorationViewOfKind:inKind
withIndexPath:inIndexPath];

theAttributes.size = CGSizeMake(70.0, 70.0);
theAttributes.center =
CGPointMake(theWidth – 70.0, 114.0);
return theAttributes;
}

Listing 5.105 Erzeugung der Layoutattribute für einen Decorationview



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück




Copyright © Rheinwerk Verlag GmbH, Bonn 2014
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.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern


  Zum Katalog
Zum Katalog: Apps programmieren für iPhone und iPad






Neuauflage: Apps programmieren für iPhone und iPad
Jetzt Buch 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: Spieleprogrammierung mit Android Studio






Spieleprogrammierung mit Android Studio


Zum Katalog: Android 5






Android 5


Zum Katalog: iPhone und iPad-Apps entwickeln






iPhone und iPad-Apps entwickeln


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo