web-dev-qa-db-fra.com

le comportement de UICollectionViewFlowLayout n'est pas défini, car la largeur de la cellule est supérieure à la largeur de collectionView

2015-08-18 16: 07: 51.523 Exemple [16070: 269647] du comportement du UICollectionViewFlowLayout n'est pas défini parce que: 2015-08-18 16: 07: 51.523
Exemple [16070: 269647], la largeur de l'élément doit être inférieure à la largeur de UICollectionView moins les insertions de section laissées et les valeurs de droite, moins les valeurs insérées dans le contenu.
2015-08-18 16: 07: 51.524 Exemple [16070: 269647] Informations pertinentes L’instance UICollectionViewFlowLayout est, et elle est attachée à; animations = { position =; bounds.Origin =; bounds.size =; }; couche =; contentOffset: {0, 0}; contentSize: {1024, 770}> disposition de la vue collection: . 2015-08-18 16: 07: 51.524 Exemple [16070: 269647] Créez une symbolique point d'arrêt à UICollectionViewFlowLayoutBreakForInvalidSizes à intercepter ceci dans le débogueur.

C'est ce que je reçois, ce que je fais c'est 

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        return CGSizeMake(self.collectionView!.frame.size.width - 20, 66)
    }

lorsque je bascule de paysage en portrait, la console affiche ce message d'erreur uniquement dans iOS 9: est-ce que quelqu'un sait ce que cela se produit et s'il existe une solution à ce problème?

 Screenshot

34
Shabarinath Pabba

Cela se produit lorsque la vue de votre collection est redimensionnée à une taille inférieure (passez du mode paysage au mode portrait, par exemple) et que la cellule devient trop grande pour s'adapter.

Pourquoi la cellule est-elle en train de devenir trop grande, alors que la disposition de flux de vue de collection doit être appelée et renvoyer une taille appropriée?

collectionView (collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath)

Mise à jour pour inclure Swift 4

@objc override func collectionView (_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {...}

Ceci est dû au fait que cette fonction est appelée pas, ou du moins pas tout de suite.

En effet, votre sous-classe de présentation du flux de vue de collection ne remplace pas la fonction shouldInvalidateLayoutForBoundsChange, qui returns false par défaut.

Lorsque cette méthode renvoie la valeur false, la vue de collection essaie d'abord de prendre la taille de cellule actuelle, détecte un problème (qui enregistre l'avertissement), puis appelle la structure de flux pour redimensionner la cellule.

Cela signifie 2 choses:

1 - l'avertissement en soi n'est pas nocif

2 - vous pouvez vous en débarrasser en substituant simplement la fonction shouldInvalidateLayoutForBoundsChange pour qu'elle renvoie true . Dans ce cas, la présentation du flux sera toujours appelée lorsque les limites de la vue de collection changent.

27
AirXygène

Cela arrive parce que la largeur de votre cellule de vue de collection est supérieure à celle de la collection après rotation. Supposons que vous avez un écran 1024x768 et que votre vue de collection remplit l'écran. Lorsque votre appareil est en mode paysage, la largeur de votre cellule sera self.collectionView.frame.size. .width - 20 = 1004 et sa largeur supérieure à celle de votre vue de collection en mode portrait = 768.Le débogueur indique que "la largeur de l'élément doit être inférieure à la largeur de UICollectionView moins les valeurs des incrustations de section gauche et droite, moins les incrustations de contenu restantes et bonnes valeurs ".

5
mohammadrhemmati

J'utilise une sous-classe de UICollectionViewFlowLayout et j'utilise sa propriété itemSize pour spécifier la taille de la cellule (au lieu de la méthode déléguée collectionView:sizeForItemAtIndexPath:). Chaque fois que je fais pivoter l’écran sur une largeur plus petite (par exemple, paysage -> portrait), je reçois cet énorme avertissement en question.

J'ai été capable de le réparer en faisant 2 étapes.

Étape 1: Dans la fonction prepareLayout() de la sous-classe UICollectionViewFlowLayout, déplacez super.prepareLayout() vers aprèsself.itemSize est défini. Je pense que cela oblige la super classe à utiliser la valeur itemSize correcte.

import UIKit

extension UICollectionViewFlowLayout {

  var collectionViewWidthWithoutInsets: CGFloat {
    get {
      guard let collectionView = self.collectionView else { return 0 }
      let collectionViewSize = collectionView.bounds.size
      let widthWithoutInsets = collectionViewSize.width
        - self.sectionInset.left - self.sectionInset.right
        - collectionView.contentInset.left - collectionView.contentInset.right
      return widthWithoutInsets
    }
  }

}

class StickyHeaderCollectionViewLayout: UICollectionViewFlowLayout {

  // MARK: - Variables
  let cellAspectRatio: CGFloat = 3/1

  // MARK: - Layout
  override func prepareLayout() {
    self.scrollDirection = .Vertical
    self.minimumInteritemSpacing = 1
    self.minimumLineSpacing = 1
    self.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: self.minimumLineSpacing, right: 0)
    let collectionViewWidth = self.collectionView?.bounds.size.width ?? 0
    self.headerReferenceSize = CGSize(width: collectionViewWidth, height: 40)

    // cell size
    let itemWidth = collectionViewWidthWithoutInsets
    self.itemSize = CGSize(width: itemWidth, height: itemWidth/cellAspectRatio)

    // Note: call super last if we set itemSize
    super.prepareLayout()
  }

  // ...

}

Notez que la modification ci-dessus modifiera la taille de la mise en page lorsque la rotation de l'écran cessera de fonctionner. C'est là qu'intervient l'étape 2.

Étape 2: Placez ceci dans le contrôleur de vue qui contient la vue de collection.

  override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    collectionView.collectionViewLayout.invalidateLayout()
  }

Maintenant l'avertissement est parti :)


Quelques notes:

  1. Assurez-vous que vous ajoutez des contraintes à collectionView et que vous n'utilisez pas collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight].

  2. Assurez-vous que vous n'appelez pas invalidateLayout dans viewWillTransitionToSize() car la largeur d'une cellule bord à bord dans le paysage est plus grande que la largeur du cadre de la vue de collection en mode portrait. Voir les références ci-dessous.

Références

5
Hlung

J'ai résolu ce problème en utilisant safeAreaLayoutGuide.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    return CGSize(width: (view.safeAreaLayoutGuide.layoutFrame.width), height: 80);
}

et vous devez également remplacer cette fonction pour prendre en charge correctement les modes portrait et paysage.

 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    collectionView?.collectionViewLayout.invalidateLayout();
}
4
Luis Santiago

Vous pouvez vérifier dans le débogueur si collectionView.contentOffset est devenu négatif dans mon cas, il passe de (0,0) à (0,-40). Vous pouvez résoudre ce problème en utilisant cette méthode 

if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) {
   self.automaticallyAdjustsScrollViewInsets = NO;
}
3
Anshul

Après quelques expériences, cela semble également lié à la façon dont vous organisez votre collectionView. 

Le tl; dr est: Utilisez AutoLayout, pas autoresizingMask.

Donc, pour le cœur du problème, les meilleures solutions que j'ai trouvées pour gérer le changement d'orientation utilisent le code suivant, ce qui est logique:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    coordinator.animate(alongsideTransition: { (context) in
        self.collectionView.collectionViewLayout.invalidateLayout()
    }, completion: nil)
}

Cependant, il existe toujours des situations où vous pouvez obtenir un avertissement concernant la taille de l'élément. Pour moi, si je suis en mode paysage dans un onglet, passez à un autre onglet, passez en portrait, puis revenez à l’onglet précédent. J'ai essayé d'invalider la mise en page dans willAppear, willLayout, tous les suspects habituels, mais pas de chance. En fait, même si vous appelez invalidateLayoutavantsuper.willAppear(), vous obtenez toujours l'avertissement. 

Et finalement, ce problème est lié à la taille de la mise à jour des limites de collectionView avant que le délégué ne soit invité à indiquer la taille de l'article.

Donc, avec cela à l'esprit, j'ai essayé d'utiliser AutoLayout au lieu de collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight], et avec cela, le problème est résolu! (J'utilise SnapKit pour faire l'AutoLayout afin de ne pas me tirer les cheveux de façon constante). Vous devez également juste invalidateLayout dans viewWillTransition (sans le coordinator.animate, comme dans votre exemple) et aussi invalidateLayout au bas de viewWillAppear(:). Cela semble couvrir toutes les situations.

Je ne sais pas si vous utilisez le redimensionnement automatique ou non - il serait intéressant de savoir si ma théorie/solution fonctionne pour tout le monde.

2
Loz

J'ai également constaté que cela se produisait lorsque nous définissions la propriété installedItemSize sur automaticSize et que la taille calculée des cellules est inférieure à 50x50 (Remarque: cette réponse était valide à l'époque d'iOS 12. Les versions ultérieures l'ont peut-être corrigée).

par exemple, si vous déclarez prendre en charge les cellules à dimensionnement automatique en définissant la taille estimée de l'élément sur la constante automaticSize:

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

et vos cellules sont en réalité plus petites que 50x50 points, puis UIKit imprime ces avertissements. FWIW, les cellules sont finalement dimensionnées de manière appropriée et les avertissements semblent inoffensifs.

Un réparer La solution de contournement consiste à remplacer la constante par une taille d'élément estimée de 1x1:

flowLayout.estimatedItemSize = CGSize(width: 1, height: 1)

Cela n’a pas d’incidence sur la prise en charge du dimensionnement automatique, car, comme indiqué dans la documentation de estimatedItemSize:

Si vous définissez cette valeur sur une autre valeur, la vue de collection interroge chaque cellule sur sa taille réelle à l'aide de la méthode preferredLayoutAttributesFitting (_ :) de la cellule. 

Cependant, cela pourrait/pourrait ne pas avoir d’incidences sur les performances.

2
Manav

Cela fonctionne pour moi:

Invalide la mise en page en rotation:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    collectionView.collectionViewLayout.invalidateLayout()
}

Avoir une fonction séparée pour calculer la largeur disponible:

Remarque: en tenant compte à la fois de l’encart de section et de l’encart de contenu.

// MARK: - Helpers

func calculateCellWidth(for collectionView: UICollectionView, section: Int) -> CGFloat {
    var width = collectionView.frame.width
    let contentInset = collectionView.contentInset
    width = width - contentInset.left - contentInset.right

    // Uncomment the following two lines if you're adjusting section insets
    // let sectionInset = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, insetForSectionAt: section)
    // width = width - sectionInset.left - sectionInset.right

    // Uncomment if you are using the sectionInset property on flow layouts
    // let sectionInset = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset ?? UIEdgeInsets.zero
    // width = width - sectionInset.left - sectionInset.right

    return width
}

Et bien sûr, finalement, en retournant la taille de l'article:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: calculateCellWidth(for: collectionView, section: indexPath.section), height: 60)
}

Je pense qu'il est important de noter que cela fonctionne car l'invalidation d'une mise en page ne déclenchera pas immédiatement un nouveau calcul de la taille de la cellule, mais lors du prochain cycle de mise à jour de la mise en page. Cela signifie qu’une fois le rappel de la taille de l’élément appelé, la vue de la collection dispose du cadre approprié, ce qui permet un dimensionnement précis.

Voila!

2
josh-fuggle

J'ai eu un problème similaire.

Dans mon cas, j'avais une vue de collection et lorsque vous avez tapé sur l'une des cellules, un popover avec un UITextField ouvert, pour modifier l'élément. Une fois que ce popover a disparu, le self.collectionView.contentInset.bottom a été défini sur 55 (à l'origine 0).

Pour résoudre mon problème, après la disparition de la vue contextuelle, je règle manuellement contentInset sur UIEdgeInsetsZero.

contextual prediction bar

Le problème initial semble être lié à la barre de prédiction contextuelle qui apparaît au-dessus du clavier. Lorsque le clavier est masqué, la barre disparaît mais la valeur contentInset.bottom n'est pas restaurée à la valeur d'origine.

Puisque votre problème semble être lié à la largeur et non à la hauteur de la cellule, vérifiez si l’une des valeurs contentInset ou layout.sectionInset est identique à celle que vous avez définie.

1
Marius

J'ai eu le même problème. J'ai résolu ce problème en supprimant les contraintes de ma collection et en les réinitialisant dans Storyboard. 

1
Dayna

J'ai trouvé que cela fonctionnait assez bien pourUICollectionViewControlleret animé correctement:

- (void)viewWillTransitionToSize:(CGSize)size
       withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    // Ensure the layout is within the allowed size before animating below, or 
    //  `UICollectionViewFlowLayoutBreakForInvalidSizes` breakpoint will trigger
    //  and the logging will occur.

    if ([self.collectionView.collectionViewLayout isKindOfClass:[YourLayoutSubclass class]]) {
        [(YourLayoutSubclass *)self.collectionView.collectionViewLayout updateForWidth:size.width];
    }

    // Then, animate alongside the transition, as you would:

    [coordinator animateAlongsideTransition:^
        (id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
            [self.collectionView.collectionViewLayout invalidateLayout];
        }
                                 completion:nil];

    [super viewWillTransitionToSize:size
          withTransitionCoordinator:coordinator];
}

///////////////

@implementation YourLayoutSubclass

- (void)prepareLayout {
    [super prepareLayout];

    [self updateForWidth:self.collectionView.bounds.size.width];
}

- (void)updateForWidth:(CGFloat)width {
    // Update layout as you wish...
}

@end

... Voir les commentaires de code en ligne ci-dessus pour des explications. ????????

0
Ben Guild