UICollectionView Layout

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • UICollectionView Layout

    Hi, ich bin mich am Einarbeiten mit UICollectionView, bzw. bisschen am rumtesten ;)

    Jetz möcht ich aber meinem CollectionView ein anderes Layout verpassen.

    Wie stell ich das an?

    Ich kann zwar im InterfaceBuilder zwischen Flow und Custom auswählen, aber gibts da sonst noch nix fertiges?

    Hab mir grad mal das WWDC Video Introducing Collection Views angesehen, da hat er auch mehrere Layouts.

    Danke

    Edit: Dieses Layout möchte ich erstellen
  • matz schrieb:

    Ich kann zwar im InterfaceBuilder zwischen Flow und Custom auswählen, aber gibts da sonst noch nix fertiges?

    Momentan stellt Apple nur das Flowlayout als fertiges Layout zur Verfügung. Das allerdings recht vielseitig ist und Du kannst es auch als Basis für eigene Layouts verwenden. Ansonsten nimmst Du UICollectionViewLayout als Basis.

    Bei Deinem Beispiel würde ich es zunächst einmal mit einem Flowlayout probieren.
    „Meine Komplikation hatte eine Komplikation.“
  • Ein gestacktes Layout ist gar nicht so schwer.
    Hab mich da vor 1-2 Wochen auch durchgekämpft.

    Du brauchst 2 UICollectionViews, nicht eine.
    Die erste zeigt die Alben mit den "Fake-Stacks" an.
    Die zweite wird dann beim Tap auf eine dieser Zellen im gestackten Layout an der selben Stelle wie die Zelle angezeigt.
    Der Wechsel ist für den Anwender ja nicht sichtbar solang beides identisch aussieht.
    Dann eben die Foto-CollectionView zum Flow-Layout animieren und die Alben-CollectionView ausfaden.

    Apples Foto App macht das genauso.
    Man sieht den Wechsel zwischen den beiden Collectionviews wenn man ein Album wieder schließt.
    Im letzten Moment der Animation werden die Kanten schärfer und die Schatten der einzelnen Fotos wirken plötzlich anders.
    Das ist der Wechsel zwischen der sich zusammenziehenden PhotoCollectionView zur vorgerenderten Zelle der AlbumCollectionView.


    Habe das auch gerade in einer kleinen Anwendung eingebaut, welche mit Fotos hantiert und einer Idee welche erst wegen einem anderen neuen iOS6 Feature entstanden ist.
    Daher war iOS 6 als Minimum kein Hindernis und die Collectionview zu verlockend. ;)

    Du kannst das problemlos mit einer FlowLayout Subclass erreichen.

    Am wichtigsten ist die Methode:

    Quellcode

    1. -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
    2. NSArray* attributesArray = [super layoutAttributesForElementsInRect:rect];
    3. // Hier hast du schon die Attributes (also Position usw.) von allen Zellen, welche die CollectionView in dieser Scrollposition anzeigen würde.
    4. // Musst an sich nur attributes.center, attributes.transform3D und attributes.zIndex anpassen.
    5. // ...
    6. }


    Zusätzlich hab ich der FlowLayout Subclass dann noch eine property stackCenter und stackFactor gegeben.
    stackCenter ist die Position zu der sich der Stack zusammenzieht, stackFactor gibt an wie weit er sich im Moment zusammengezogen haben soll.
    stackFactor = 0.0 wäre komplett gestackt, stackFactor 1.0 wäre normales FlowLayout.

    Dazu berechne ich in der obigen Methode eben den normalen (ungestackten) X- und Y-Abstand zum stackCenter und multipliziere das mit dem stackFactor.

    Das coole ist, dass die Animation durch performBatchUpdates automatisch abläuft solang man mit der Standard Animationsgeschwindigkeit zufrieden ist.
    Man kann also seine UICollectionView gestackt sichtbar machen und dann einfach folgenden Code ausführen:

    Quellcode

    1. [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
    2. self.albumCollectionView.alpha = 0.0;
    3. } completion:nil];
    4. [self.albumCollectionView performBatchUpdates:^{
    5. self.photoCollectionView.collectionViewLayout.stackFactor = 1.0;
    6. } completion:nil];


    Dem Layout der AlbumCollectionView habe ich noch eine Property indexPathToHide gegeben.
    ist diese != nil wird in der layoutAttributesForElementsInRect: Methode dieser FlowLayout-Subclass für die Cell attributes.alpha auf 0.0 gesetzt.
    Wenn ich die gestackte PhotoCollectionView sichtbar mache, kann ich so die original Cell aus der AlbumCollectionView somit ausblenden:

    Quellcode

    1. self.photoCollectionView.collectionViewLayout.stackCenter = aufAndereCollectionViewUmgerechneteCenterKoordinatenDerAngeklicktenAlbumCell...;
    2. self.photoCollectionView.collectionViewLayout.stackFactor = 0.0;
    3. [self.photoCollectionView.collectionViewLayout invalidateLayout];
    4. self.albumCollectionView.collectionViewLayout.indexPathToHide = indexPathDerAngeklicktenCell...;
    5. [self.albumCollectionView.collectionViewLayout invalidateLayout];
    6. self.photoCollectionView.hidden = NO;


    Ist an sich fast mehr Text zu erklären als es umzusetzen. :P
    Die FlowLayout Subclass hat für den hier benötigten Spaß gerade mal um die 50 Zeilen Code.
  • Zu einem vollen Beispiel reicht mir die Zeit leider nicht,
    aber hier mal die FlowLayout Subclass.
    Ist von Code-Aufwand wirklich lächerlich:

    Quellcode

    1. #import <UIKit/UIKit.h>
    2. @interface StackLayout : UICollectionViewFlowLayout
    3. // Punkt zu dem sich der Stack zusammenzieht
    4. @property (nonatomic) CGPoint stackCenter;
    5. // 0.0 = komplett gestacked, 1.0 normale FlowLayout Anordnung
    6. @property (nonatomic) CGFloat stackFactor;
    7. @end


    Quellcode

    1. #import "StackLayout.h"
    2. @implementation StackLayout
    3. // Custom Setter um das Neuzeichnen den Layouts zu erzwingen
    4. - (void)setStackFactor:(CGFloat)stackFactor {
    5. _stackFactor = stackFactor;
    6. [self invalidateLayout];
    7. }
    8. // Man kann die Zellen nur innerhalb der contentView anordnen.
    9. // Bei zu wenig Items (und somit zu kleiner ContentView) wäre also nicht
    10. // jede Position innerhalb der Collectionview als stackCenter möglich
    11. // (Fehlerhafte Animation).
    12. // Daher Mindestgröße der contentSize auf self.collectionView.bounds.size setzen.
    13. -(CGSize)collectionViewContentSize {
    14. CGSize contentSize = [super collectionViewContentSize];
    15. if (self.collectionView.bounds.size.width > contentSize.width)
    16. contentSize.width = self.collectionView.bounds.size.width;
    17. if (self.collectionView.bounds.size.height > contentSize.height)
    18. contentSize.height = self.collectionView.bounds.size.height;
    19. return contentSize;
    20. }
    21. -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
    22. NSArray* attributesArray = [super layoutAttributesForElementsInRect:rect];
    23. // Für jede Cell die neue Position anhand von StackFactor und StackCenter errechnen
    24. for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
    25. CGFloat xPosition = self.stackCenter.x + (attributes.center.x - self.stackCenter.x) * self.stackFactor;
    26. CGFloat yPosition = self.stackCenter.y + (attributes.center.y - self.stackCenter.y) * self.stackFactor;
    27. attributes.center = CGPointMake(xPosition, yPosition);
    28. if (attributes.indexPath.row == 0) {
    29. attributes.alpha = 1.0;
    30. attributes.zIndex = 1.0; // Erste Zelle ganz nach oben
    31. } else {
    32. attributes.alpha = self.stackFactor; // die unteren Zellen langsam ausfaden
    33. attributes.zIndex = 0.0; // Restliche Zellen darunter schieben
    34. }
    35. }
    36. return attributesArray;
    37. }
    38. @end
    Alles anzeigen


    Wenn du das Layout einer CollectionView zuweist,
    und ihr z.B. bei viewDidLoad ein stackCenter und stackFactor = 0.0 zuweist,
    sollte der Content direkt wie eine einzelne Zelle aussehen.

    machst du dann später irgendwo:

    Quellcode

    1. [myCollectionView performBatchUpdates:^{
    2. myCollectionView.collectionViewLayout.stackFactor = 1.0;
    3. } completion:nil];

    sollte die CollectionView den Übergang zum GitterLayout animieren.
  • Ich bin auch gerade dabei, so ein Layout zu machen. Dafür habe ich mir eine Flow Layout Klasse erstellt und dort den Code eingefügt. In meinen CollectionViewController habe ich folgenden Code geschrieben:

    Quellcode

    1. - (void)viewDidLoad
    2. {
    3. [super viewDidLoad];
    4. // Do any additional setup after loading the view.
    5. test = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil];
    6. [self.collectionView registerClass:[TestCollectionViewCell class] forCellWithReuseIdentifier:@testCell"];
    7. //CollectionViewFlowLayout *flowLayout = (CollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
    8. //[flowLayout setStackFactor:0.0];
    9. }
    10. - (void)didReceiveMemoryWarning
    11. {
    12. [super didReceiveMemoryWarning];
    13. // Dispose of any resources that can be recreated.
    14. }
    15. -(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    16. return 1;
    17. }
    18. -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    19. return self.testArray.count;
    20. }
    21. -(UICollectionViewCell *)collectionView:(UICollectionView *)theCollectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    22. static NSString *itemIdentifier = @"testCell";
    23. TestCollectionViewCell *item = (TestCollectionViewCell *)[theCollectionView dequeueReusableCellWithReuseIdentifier:itemIdentifier forIndexPath:indexPath];
    24. // Configure the cell...
    25. item.textLabel.text = [self.tafelwerkArray objectAtIndex:indexPath.row+2*indexPath.section];
    26. return it
    Alles anzeigen

    Dieser Code funktioniert auch bei einen normalen Flow Layout. Was muss ich umändern, damit dieser Code auch bei dieser Layout-Klasse funktioniert? Liegt es daran, dass ich eine eigene Celle mir erstellt habe und diese nur ein Label beinhalted?
    Viele grüße
    Nils
  • Zuerst erstellst du in viewDidLoad ein Array das du "test" zuweist.
    Eine Instanzvariable?

    Wenn ja dann ist das Ding nach verlassen von viewDidLoad weg.
    Speicherverwaltung!

    In den anderen Methoden verwendest du dann "testArray".
    Zumindest in deinem geposteten Code wird das nirgends initialisiert oder befüllt.

    Sofern du nicht zwei Arrays (test und testArray) als Properties / iVars angelegt hast sollte das normal beim Compilieren nicht nur Warnings geben sondern Fehler.

    Da ich gerade die Freitag-Gute-Laune habe, hänge ich mal mein Testprojekt von eben mit an. ;)
  • Da musst dir eine eigene Berechnung im Layout für die Positionen erstellen.

    Oder es so machen wie in Beitrag #6 gesagt: Apple verwendet ZWEI UICollectionViews für die Darstellung der Alben und der Photos.
    Die erste würde die Fake Stacks für deine Sections darstellen (also eine Zelle pro derzeitiger Section),
    die zweite dann die Zellen einer Section.
  • Ich habe jetzt noch mal genau überlegt. Wenn ich nur zwei CollectionViews nehme aber 3 Sections habe, woher weiß der dann welche Items der laden soll? Ich hätte jetzt 3 CollectionViews, je einen für eine Section erstellt. Das ist aber nicht die potimalste Lösung. Muss ich da bevor ich den CollectionView per perfomSeque anzeigen bei diesen eine Methode aufrufen, die festlegt, welche Daten er laden soll oder geht das noch elleganter?