web-dev-qa-db-fra.com

UICollectionView aligner la logique manquante dans la vue de défilement de la pagination horizontale

J'ai un UICollectionView, qui fonctionne bien, jusqu'à ce que je commence à faire défiler. Voici quelques photos en premier: enter image description here

Comme vous pouvez le voir, c'est super. Lorsque je commence à faire défiler (la pagination est activée), la première passe un peu hors écran: enter image description here

C'est le problème. Originalement ma vue a 3 vues et je veux faire défiler et afficher 3 vues seulement. Mais en défilant (la pagination est activée), elle cache un peu de la première vue et montre un peu de la première vue suivante de la page suivante.

Et voici une vidéo, car c'est un peu difficile à expliquer: Vidéo du problème (Dropbox)

Voici une photo de mes paramètres UICollectionViewenter image description here

Ça va être génial si quelqu'un peut aider!

56
Devfly

Le problème fondamental est que Flow Layout n'est pas conçu pour prendre en charge la pagination. Pour obtenir l'effet de pagination, vous devrez sacrifier l'espace entre les cellules. Et calculez soigneusement le cadre des cellules et faites-le peut être divisé par le cadre de la vue de collection sans reste. J'expliquerai la raison.

Dire la mise en page suivante est ce que vous vouliez.

enter image description here

Remarquez que la marge la plus à gauche (verte) ne fait pas partie de l'espacement des cellules. Elle est déterminée par l'encart de la section de disposition de flux. Étant donné que la disposition du flux ne prend pas en charge la valeur d'espacement hétérogène. Ce n'est pas une tâche banale.

Par conséquent, après avoir réglé l'espacement et l'encart. La disposition suivante est ce que vous obtiendrez.

enter image description here

Après avoir fait défiler la page suivante. Vos cellules ne sont évidemment pas alignées comme prévu.

enter image description here

Faire l'espacement de cellule 0 peut résoudre ce problème. Cependant, cela limite votre conception si vous voulez la marge supplémentaire sur la page, surtout si la marge est différente de l'espacement des cellules. Cela nécessite également que le cadre de vue soit divisible par le cadre de cellule. Parfois, c'est pénible si votre cadre de vue n'est pas fixe (compte tenu du cas de rotation).

La vraie solution consiste à sous-classer UICollectionViewFlowLayout et à remplacer les méthodes suivantes

- (CGSize)collectionViewContentSize
{
    // Only support single section for now.
    // Only support Horizontal scroll 
    NSUInteger count = [self.collectionView.dataSource collectionView:self.collectionView
                                               numberOfItemsInSection:0];

    CGSize canvasSize = self.collectionView.frame.size;
    CGSize contentSize = canvasSize;
    if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
    {
        NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
        NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;
        NSUInteger page = ceilf((CGFloat)count / (CGFloat)(rowCount * columnCount));
        contentSize.width = page * canvasSize.width;
    }

    return contentSize;
}


- (CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize canvasSize = self.collectionView.frame.size;

    NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
    NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;

    CGFloat pageMarginX = (canvasSize.width - columnCount * self.itemSize.width - (columnCount > 1 ? (columnCount - 1) * self.minimumLineSpacing : 0)) / 2.0f;
    CGFloat pageMarginY = (canvasSize.height - rowCount * self.itemSize.height - (rowCount > 1 ? (rowCount - 1) * self.minimumInteritemSpacing : 0)) / 2.0f;

    NSUInteger page = indexPath.row / (rowCount * columnCount);
    NSUInteger remainder = indexPath.row - page * (rowCount * columnCount);
    NSUInteger row = remainder / columnCount;
    NSUInteger column = remainder - row * columnCount;

    CGRect cellFrame = CGRectZero;
    cellFrame.Origin.x = pageMarginX + column * (self.itemSize.width + self.minimumLineSpacing);
    cellFrame.Origin.y = pageMarginY + row * (self.itemSize.height + self.minimumInteritemSpacing);
    cellFrame.size.width = self.itemSize.width;
    cellFrame.size.height = self.itemSize.height;

    if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
    {
        cellFrame.Origin.x += page * canvasSize.width;
    }

    return cellFrame;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes * attr = [super layoutAttributesForItemAtIndexPath:indexPath];
    attr.frame = [self frameForItemAtIndexPath:indexPath];
    return attr;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray * attrs = [NSMutableArray array];

    [originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
        NSIndexPath * idxPath = attr.indexPath;
        CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
        if (CGRectIntersectsRect(itemFrame, rect))
        {
            attr = [self layoutAttributesForItemAtIndexPath:idxPath];
            [attrs addObject:attr];
        }
    }];

    return attrs;
}

Notez que l'extrait de code ci-dessus ne prend en charge qu'une seule section et la direction de défilement horizontal. Mais il n'est pas difficile de se développer.

Aussi, si vous n'avez pas des millions de cellules. La mise en cache de ces UICollectionViewLayoutAttributes peut être une bonne idée.

76
Ji Fang

Vous pouvez désactiver la pagination sur UICollectionView et implémenter un mécanisme de défilement/pagination horizontal personnalisé avec une largeur/décalage de page personnalisé comme ceci:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    float pageWidth = 210;

    float currentOffset = scrollView.contentOffset.x;
    float targetOffset = targetContentOffset->x;
    float newTargetOffset = 0;

    if (targetOffset > currentOffset)
        newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth;
    else
        newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth;

    if (newTargetOffset < 0)
        newTargetOffset = 0;
    else if (newTargetOffset > scrollView.contentSize.width)
        newTargetOffset = scrollView.contentSize.width;

    targetContentOffset->x = currentOffset;
    [scrollView setContentOffset:CGPointMake(newTargetOffset, 0) animated:YES];
}
28
Leszek Szary

Cette réponse est bien en retard, mais je viens de jouer avec ce problème et j'ai découvert que la cause de la dérive est l'espacement des lignes . Si vous voulez que la page UICollectionView/FlowLayout page aux multiples exacts de la largeur de vos cellules, vous devez définir:

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
flowLayout.minimumLineSpacing = 0.0;

Vous ne penseriez pas que l'espacement des lignes entre en jeu dans le défilement horizontal, mais apparemment, c'est le cas.

Dans mon cas, j'expérimentais la radiomessagerie de gauche à droite, une cellule à la fois, sans espace entre les cellules. Chaque tour de page introduisait un tout petit peu de dérive par rapport à la position souhaitée, et il semblait s'accumuler de façon linéaire. ~ 10,0 pts par tour . J'ai réalisé que 10.0 est la valeur par défaut de minimumLineSpacing dans la disposition du flux. Quand je l'ai mis à 0,0 , pas de dérive, quand je l'ai mis à la moitié de la largeur des limites, chaque page a dérivé une moitié supplémentaire des limites.

La modification de minimumInteritemSpacing a eu aucun effet.

modifier - à partir de la documentation pour ICollectionViewFlowLayout :

@property (nonatomic) CGFloat minimumLineSpacing;

Discussion

...

Pour une grille à défilement vertical, cette valeur représente l'espacement minimum entre les lignes successives. Pour une grille à défilement horizontal, cette valeur représente l'espacement minimum entre les colonnes successives. Cet espacement n'est pas appliqué à l'espace entre l'en-tête et la première ligne ou entre la dernière ligne et le pied de page.

La valeur par défaut de cette propriété est 10,0.

19
devdavid

enter image description here

La solution de l'article suivant est élégante et simple. L'idée principale est de créer le scrollView au-dessus de votre collectionView en passant toutes les valeurs contentOffset.

http://b2cloud.com.au/tutorial/uiscrollview-paging-size/

Il faut le dire en implémentant cette méthode:

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;

Je n'ai pas réalisé une animation fluide comme cela se produit avec pagingEnabled = YES.

9
nikolsky

Je sais que cette question est ancienne, mais pour tous ceux qui se heurtent à cela.

Tout ce que vous avez à faire pour corriger cela est de définir le MinimumInterItemSpacing à 0 et de réduire le cadre du contenu.

7
Jonathan

@devdavid était à zéro sur le flowLayout.minimumLineSpacing.

Cela peut également être fait dans l'éditeur de disposition, en définissant l'espacement minimum pour les lignes sur 0:

Easier Way

6
erickva

Je pense que je comprends le problème. Je vais essayer de te le faire comprendre aussi.

Si vous regardez attentivement, vous verrez que ce problème ne se produit que progressivement et pas seulement lors du premier balayage de page.

enter image description here

Si je comprends bien, dans votre application, actuellement, tous les éléments UICollectionView sont les cases arrondies que nous voyons, et vous avez un décalage/une marge entre tous, ce qui est constant. C'est ce qui cause le problème.

Au lieu de cela, vous devez créer un élément UICollectionView qui représente 1/3 de la largeur de la vue entière, puis ajouter cette image arrondie à l'intérieur. Pour faire référence à l'image, la couleur verte doit être votre UICollectionViewItem et non la noire.

6
raz0r

Vous roulez votre propre UICollectionViewFlowLayout?

Si c'est le cas, l'ajout de -(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity vous aidera à calculer où la vue de défilement doit s'arrêter.

Cela pourrait fonctionner (NB: UNTESTED!):

-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
                             withScrollingVelocity:(CGPoint)velocity
{
  CGFloat offsetAdjustment = MAXFLOAT;
  CGFloat targetX = proposedContentOffset.x + self.minimumInteritemSpacing + self.sectionInset.left;

  CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);

  NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
  for(UICollectionViewLayoutAttributes *layoutAttributes in array) {

    if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
      CGFloat itemX = layoutAttributes.frame.Origin.x;

      if (ABS(itemX - targetX) < ABS(offsetAdjustment)) {
        offsetAdjustment = itemX - targetX;
      }
    }
  }

  return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
3
Audun Kjelstrup

Ma réponse est basée sur la réponse https://stackoverflow.com/a/27242179/440168 mais est plus simple.

enter image description here

Vous devez placer UIScrollView au-dessus de UICollectionView et leur donner des tailles égales:

@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;

Configurez ensuite contentInset de la vue de collection, par exemple:

CGFloat inset = self.view.bounds.size.width*2/9;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);

Et contentSize de la vue de défilement:

self.scrollView.contentSize = CGSizeMake(self.placesCollectionView.bounds.size.width*[self.collectionView numberOfItemsInSection:0],0);

N'oubliez pas de définir le délégué de la vue de défilement:

self.scrollView.delegate = self;

Et implémentez la magie principale:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView == self.scrollView) {
        CGFloat inset = self.view.bounds.size.width*2/9;
        CGFloat scale = (self.placesCollectionView.bounds.size.width-2*inset)/scrollView.bounds.size.width;
        self.collectionView.contentOffset = CGPointMake(scrollView.contentOffset.x*scale - inset, 0);
    }
}
3
k06a

la largeur de votre UICollectionView doit être une multiplication exacte de la largeur de la taille de la cellule + les encarts gauche et droit. Dans votre exemple, si la largeur de cellule est 96, la largeur de UICollectionView doit être (96 + 5 + 5) * 3 = 318. Ou, si vous souhaitez conserver la largeur 320 d'UICollectionView, la largeur de la taille de votre cellule doit être 320 / 3 - 5 - 5 = 96.666.

Si cela n'aide pas, la largeur de votre UICollectionView peut être différente de celle définie dans le fichier xib, lorsque l'application s'exécute. Pour vérifier cela - ajoutez une instruction NSLog pour imprimer la taille de la vue lors de l'exécution:

NSLog(@"%@", NSStringFromCGRect(uiContentViewController.view.frame));
2
gardenofwine

vous pouvez également définir la largeur de la vue sur "320 + espacement", puis définir l'activation de la page sur oui. il fera défiler "320 + espacement" à chaque fois. je pense que parce que la page active fera défiler la largeur de la vue mais pas la largeur de l'écran.

1
david

C'est le même problème que je rencontrais et j'ai posté ma solution sur un autre post, donc je la posterai à nouveau ici.

J'ai trouvé une solution et cela impliquait de sous-classer UICollectionViewFlowLayout.

Ma taille CollectionViewCell est de 302 X 457 et j'ai défini un espacement de ligne minimal de 18 (9 pixels pour chaque cellule)

Lorsque vous étendez à partir de cette classe, il existe quelques méthodes qui doivent être remplacées. L'un d'eux est

  • (CGSize) collectionViewContentSize

Dans cette méthode, je devais additionner la largeur totale de ce qui était dans UICollectionView. Cela inclut le ([nombre de sources de données] * widthOfCollectionViewCell) + ([nombre de sources de données] * 18)

Voici mes méthodes UICollectionViewFlowLayout personnalisées ....

-(id)init
{
    if((self = [super init])){

       self.itemSize = CGSizeMake(302, 457);
       self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
       self.minimumInteritemSpacing = 0.0f;
       self.minimumLineSpacing = 18.0f;
       [self setScrollDirection:UICollectionViewScrollDirectionHorizontal];
   }
    return self;
}



-(CGSize)collectionViewContentSize{
   return CGSizeMake((numCellsCount * 302)+(numCellsCount * 18), 457);
}

Cela a fonctionné pour moi, donc j'espère que quelqu'un d'autre le trouvera utile!

1
bolnad

J'ai rencontré un problème similaire en essayant de faire fonctionner la pagination horizontale sur une grille 3 x 3 de cellules avec des insertions de section et un espacement de cellule et de ligne.

La réponse pour moi (après avoir essayé de nombreuses suggestions - y compris le sous-classement UICollectionViewFlowLayout et diverses solutions de délégué UIScrollView) était simple. J'ai simplement utilisé des sections dans UICollectionView en divisant mon ensemble de données en sections de 9 éléments (ou moins) et en utilisant les méthodes de source de données numberOfSectionsInCollectionView et numberOfItemsInSection UICollectionView.

La pagination horizontale de l'UICollectionView fonctionne désormais à merveille. Je recommande cette approche à quiconque se déchire actuellement les cheveux dans un scénario similaire.

0
Tapfinger

Si vous utilisez la disposition de flux par défaut pour votre UICollectionView et que vous ne voulez PAS d'espace entre chaque cellule, vous pouvez définir sa propriété miniumumLineSpacing à 0 via:

((UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout).minimumLineSpacing = 0;
0
vikzilla

J'ai eu un problème similaire avec la pagination. Même si les encarts de cellule étaient tous à 0 et que la cellule avait exactement la même taille en largeur et en hauteur que le UICollectionView, la pagination n'était pas appropriée.

Ce que j'ai remarqué ressemble à un bug dans cette version (UICollectionView, iOS 6): je pouvais voir que si je travaillais avec un UICollectionView avec une largeur = 310 pixels ou plus, et une hauteur = 538 pixels, J'étais en difficulté. Cependant, si je diminuais la largeur à, disons, 300 pixels (même hauteur), les choses fonctionnaient parfaitement!

Pour une référence future, j'espère que cela aide!

0
Julio Flores

Je pense avoir une solution à ce problème. Mais je ne le fais pas si c'est le meilleur.

UICollectionViewFlowLayout contient une propriété appelée sectionInset. Ainsi, vous pouvez définir l'encart de la section selon vos besoins et créer 1 page correspondant à une section. Par conséquent, votre défilement devrait automatiquement tenir correctement dans les pages (= sections)

0
Alexander