42 votes

Réorganiser les cellules de UICollectionView

Considérons un UICollectionView avec disposition des flux et direction horizontale. Par défaut, les cellules sont ordonnées de haut en bas, de gauche à droite. Comme ceci :

1 4 7 10 13 16
2 5 8 11 14 17
3 6 9 12 15 18

Dans mon cas, la vue de la collection est paginée et elle a été conçue de manière à ce qu'un nombre spécifique de cellules tienne dans chaque page. Ainsi, un ordre plus naturel serait le suivant :

1 2 3   10 11 12
4 5 6 - 13 14 15
7 8 9   16 17 18

Quel serait le moyen le plus simple d'y parvenir, à moins d'implémenter ma propre mise en page personnalisée ? En particulier, je ne veux perdre aucune des fonctionnalités qui sont fournies gratuitement avec le logiciel UICollectionViewFlowLayout (comme les animations d'insertion/suppression).

Ou en général, comment implémenter une fonction de réordonnancement f(n) sur une disposition de flux ? La même chose pourrait s'appliquer à un ordre de droite à gauche, par exemple.

Mon approche jusqu'à présent

Ma première approche a été de sous-classer UICollectionViewFlowLayout et de passer outre layoutAttributesForItemAtIndexPath: :

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSIndexPath *reorderedIndexPath = [self reorderedIndexPathOfIndexPath:indexPath];
    UICollectionViewLayoutAttributes *layout = [super layoutAttributesForItemAtIndexPath:reorderedIndexPath];
    layout.indexPath = indexPath;
    return layout;
}

reorderedIndexPathOfIndexPath: es f(n) . En appelant super Je n'ai pas à calculer manuellement la disposition de chaque élément.

De plus, j'ai dû passer outre layoutAttributesForElementsInRect: qui est la méthode utilisée par la mise en page pour choisir les éléments à afficher.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *result = [NSMutableArray array];
    NSInteger sectionCount = 1;
    if ([self.collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
    {
        sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
    }
    for (int s = 0; s < sectionCount; s++)
    {
        NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
        for (int i = 0; i < itemCount; i++)
        {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
            UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
            if (CGRectIntersectsRect(rect, layout.frame))
            {
                [result addObject:layout];
            }
        }
    }
    return result;
}

Ici, j'essaye juste chaque élément et s'il est dans la plage de tolérance donnée rect je le renvoie.

Si cette approche est la bonne, j'ai les questions plus spécifiques suivantes :

  • Y a-t-il un moyen de simplifier le layoutAttributesForElementsInRect: de passer outre, ou de le rendre plus efficace ?
  • Est-ce que je rate quelque chose ? Au minimum, l'échange de cellules de différentes pages produit des résultats étranges. Je pense que c'est lié à initialLayoutAttributesForAppearingItemAtIndexPath: y finalLayoutAttributesForDisappearingItemAtIndexPath: mais je n'arrive pas à déterminer exactement quel est le problème.
  • Dans mon cas, f(n) dépend du nombre de colonnes et de lignes de chaque page. Existe-t-il un moyen d'extraire cette information à partir de UICollectionViewFlowLayout à moins de le coder moi-même en dur ? J'ai pensé à interrogation de layoutAttributesForElementsInRect: avec les limites de la vue de la collection, et de déduire les lignes et les colonnes à partir de là, mais cela semble également inefficace.

15voto

Bogdan Weidmann Points 755

J'ai beaucoup réfléchi à votre question et je suis arrivé aux considérations suivantes :

Le fait de sous-classer le FlowLayout semble être le moyen le plus juste et le plus efficace de réorganiser les cellules et d'utiliser les animations de mise en page du flux. Et votre approche fonctionne, à l'exception de deux choses importantes :

  1. Disons que vous avez une vue de collection avec seulement 2 cellules et que vous avez conçu votre page pour qu'elle puisse contenir 9 cellules. La première cellule sera positionnée dans le coin supérieur gauche de la vue, comme dans la mise en page originale du flux. La deuxième cellule, en revanche, doit être placée en haut de la vue et son chemin d'indexation est [0, 1]. Le chemin d'index réordonné serait [0, 3] (chemin d'index de la cellule du schéma de flux original qui serait à sa place). Et dans votre layoutAttributesForItemAtIndexPath vous enverriez le message comme [super layoutAttributesForItemAtIndexPath:[0, 3]] vous obtiendrez un nil simplement parce qu'il n'y a que deux cellules : [0,0] et [0,1]. Et ce serait le problème pour votre dernière page.
  2. Bien que vous puissiez implémenter le comportement de pagination en surchargeant la fonction targetContentOffsetForProposedContentOffset:withScrollingVelocity: et définir manuellement des propriétés comme itemSize , minimumLineSpacing y minimumInteritemSpacing En effet, il faut beaucoup de travail pour rendre les éléments symétriques, définir les bordures de la pagination, etc.

Je pense que le fait de sous-classer le schéma de flux prépare beaucoup d'implémentation pour vous, parce que ce que vous voulez n'est plus un schéma de flux. Mais nous allons y réfléchir ensemble. En ce qui concerne vos questions :

  • votre layoutAttributesForElementsInRect: est exactement comme l'implémentation originale d'apple, il n'y a donc aucun moyen de la simplifier. Pour votre cas, vous pourriez considérer ce qui suit : si vous avez 3 rangées d'éléments par page, et que le cadre de l'élément de la première rangée intersecte le cadre rect, alors (si tous les éléments ont la même taille) les cadres des éléments des deuxième et troisième rangées intersectent ce rect.
  • Désolé, je n'ai pas compris votre deuxième question.
  • dans mon cas, la fonction de réorganisation ressemble à ceci : (a est le entier nombre de rangées/colonnes sur chaque page, rangées=colonnes)

f(n) = (n % a²) + (a - 1)(col - rang) + a²(n / a²) ; col = (n % a²) % a ; rang = (n % a²) / a ;

Pour répondre à la question, le schéma de débit n'a aucune idée du nombre de lignes dans chaque colonne, car ce nombre peut varier d'une colonne à l'autre en fonction de la taille de chaque élément. Il ne peut également rien dire du nombre de colonnes sur chaque page car il dépend de la position de défilement et peut également varier. Il n'y a donc pas de meilleur moyen que d'interroger layoutAttributesForElementsInRect mais cela inclut également les cellules qui ne sont que partiellement visibles. Puisque vos cellules sont de taille égale, vous pourriez théoriquement déterminer le nombre de lignes de votre vue de la collection dans le sens du défilement horizontal : en commençant par itérer chaque cellule en les comptant et en cassant si leur frame.origin.x changements.

Donc, je pense que vous avez deux options pour atteindre votre objectif :

  1. Sous-classe UICollectionViewLayout . La mise en œuvre de toutes ces méthodes semble demander beaucoup de travail, mais c'est le seul moyen efficace. Vous pourriez avoir par exemple des propriétés comme itemSize , itemsInOneRow . Ensuite, vous pouvez facilement trouver une formule pour calculer le cadre de chaque élément sur la base de son numéro (la meilleure façon est de le faire en prepareLayout et stocker toutes les images dans un tableau, de sorte que vous puissiez accéder à l'image dont vous avez besoin dans le tableau. layoutAttributesForItemAtIndexPath ). Mise en œuvre de layoutAttributesForItemAtIndexPath , layoutAttributesForItemsInRect y collectionViewContentSize serait également très simple. Sur initialLayoutAttributesForAppearingItemAtIndexPath y finalLayoutAttributesForDisappearingItemAtIndexPath vous pouvez simplement définir l'attribut alpha sur 0,0. C'est ainsi que fonctionnent les animations de mise en page de flux standard. En remplaçant targetContentOffsetForProposedContentOffset:withScrollingVelocity: vous pourriez mettre en œuvre le "comportement de pagination".

  2. Envisagez de créer une vue de collection avec une mise en page de flux, pagingEnabled = YES La taille de l'élément est égale à la taille de l'écran et le sens du défilement est horizontal. Un élément par taille d'écran. Pour chaque cellule, vous pouvez définir une nouvelle vue de collection comme sous-vue avec une disposition de flux vertical et la même source de données que les autres vues de collection, mais avec un décalage. C'est très efficace, car vous pouvez alors réutiliser des vues de collection entières contenant des blocs de 9 (ou autre) cellules au lieu de réutiliser chaque cellule avec l'approche standard. Toutes les animations devraient fonctionner correctement.

Aquí vous pouvez télécharger un exemple de projet utilisant l'approche de sous-classement de la mise en page. (#2)

4voto

Bogdan Weidmann Points 755

Ne serait-ce pas une solution simple d'avoir 2 vues de la collection avec standart UICollectionViewFlowLayout ?

Ou encore mieux : avoir un contrôleur de vue de page avec un défilement horizontal, et chaque page serait une vue de collection avec une disposition de flux normale.

L'idée est la suivante : dans votre UICollectionViewController -init method vous créez une deuxième vue de collection avec un cadre décalé vers la droite de la largeur de votre vue de collection originale. Ensuite, vous l'ajoutez comme vue secondaire à la vue de collection originale. Pour passer d'une vue de collection à l'autre, il suffit d'ajouter un dispositif de reconnaissance du glissement. Pour calculer les valeurs de décalage, vous pouvez stocker le cadre original de la vue de la collection dans ivar. cVFrame . Pour identifier les vues de votre collection, vous pouvez utiliser des balises.

Exemple de init méthode :

CGRect cVFrame = self.collectionView.frame;
UICollectionView *secondView = [[UICollectionView alloc] 
              initWithFrame:CGRectMake(cVFrame.origin.x + cVFrame.size.width, 0, 
                             cVFrame.size.width, cVFrame.size.height) 
       collectionViewLayout:[UICollectionViewFlowLayout new]];
    [secondView setBackgroundColor:[UIColor greenColor]];
    [secondView setTag:1];
    [secondView setDelegate:self];
    [secondView setDataSource:self];
    [self.collectionView addSubview:secondView];

UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
                      initWithTarget:self action:@selector(swipedRight)];
    [swipeRight setDirection:UISwipeGestureRecognizerDirectionRight];

UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] 
                       initWithTarget:self action:@selector(swipedLeft)];
    [swipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];

    [self.collectionView addGestureRecognizer:swipeRight];
    [self.collectionView addGestureRecognizer:swipeLeft];

Exemple de swipeRight y swipeLeft méthodes :

-(void)swipedRight {
    // Switch to left collection view
    [self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
}

-(void)swipedLeft {
    // Switch to right collection view
    [self.collectionView setContentOffset:CGPointMake(cVFrame.size.width, 0) 
                                 animated:YES];
}

Et puis ce n'est pas un gros problème d'implémenter les méthodes DataSource (dans votre cas, vous voulez avoir 9 éléments sur chaque page) :

-(NSInteger)collectionView:(UICollectionView *)collectionView 
    numberOfItemsInSection:(NSInteger)section {
    if (collectionView.tag == 1) {
         // Second collection view
         return self.dataArray.count % 9;
    } else {
         // Original collection view
         return 9; // Or whatever
}

En méthode -collectionView:cellForRowAtIndexPath vous devrez récupérer les données de votre modèle avec un décalage, s'il s'agit d'une deuxième vue de collection.

N'oubliez pas non plus d'enregistrer la classe de cellule réutilisable pour votre deuxième vue de collection. Vous pouvez également ne créer qu'un seul dispositif de reconnaissance des gestes et reconnaître les balayages vers la gauche et vers la droite. C'est vous qui décidez.

Je pense que maintenant ça devrait marcher, essayez-le :-)

2voto

Henri Asseily Points 1801

Vous avez un objet qui implémente la fonction UICollectionViewDataSource protocole. À l'intérieur de collectionView:cellForItemAtIndexPath: il suffit de renvoyer le bon article que vous souhaitez retourner. Je ne comprends pas où il y aurait un problème.

Edit : ok, je vois le problème. Voici la solution : http://www.skeuo.com/uicollectionview-custom-layout-tutorial en particulier les étapes 17 à 25. Il ne s'agit pas d'un travail énorme, et il peut être réutilisé très facilement.

0voto

Narek Safaryan Points 98

Voir cet exemple
Vous pouvez réorganiser les cellules comme vous le souhaitez
https://github.com/lxcid/LXReorderableCollectionViewFlowLayout

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X