web-dev-qa-db-fra.com

Exception UICollectionView dans UICollectionViewLayoutAttributes à partir d'iOS7

J'ai fait une vue dans CollectionView avec CustomLayout. Dans iOS6, cela fonctionnait très bien mais iOS7 lance une exception comme celle-ci.

Arrêt de l'application en raison d'une exception non interceptée 'NSInternalInconsistencyException', raison:

'attributs de mise en page pour l'élément supplémentaire au chemin d'index ({length = 2, path = 0 - 0}) modifiés depuis CustomSupplementaryAttributes: 0xd1123a0 chemin d'index: (NSIndexPath: 0xd112580 {length = 2, path = 0 - 0}); type d'élément: (identifiant); cadre = (0 0; 1135,66 45); zIndex = -1; vers CustomSupplementaryAttributes: chemin d'index 0xd583c80: (NSIndexPath: 0xd583c70 {longueur = 2, chemin = 0 - 0}); type d'élément: (identifiant); cadre = (0 0; 1135,66 45); zIndex = -1; sans invalider la mise en page '

27
Jirune

Vous devez invalider la mise en page existante avant la mise à jour, voir la fin du message d'erreur:

sans invalider la mise en page '

[collectionViewLayout invalidateLayout];

Documentation Apple pour UICollectionViewLayout

18
Tim

iOS 10

Chez iOS 10, une nouvelle fonctionnalité est introduite, c'est Cell Prefetching. Cela laissera la position dynamique de SupplementaryView planter. Pour fonctionner avec l'ancien comportement, il doit désactiver prefetchingEnabled. C'est true par défaut sur iOS 10.

// Obj-C
// This function is available in iOS 10. Disable it for dynamic position of `SupplementaryView `.
if ([self.collectionView respondsToSelector:@selector(setPrefetchingEnabled:)]) {
    self.collectionView.prefetchingEnabled = false;
}

// Swift
if #available(iOS 10, *) { 
    // Thanks @maksa
    collectionView.prefetchingEnabled = false 

    // Swift 3 style
    colView.isPrefetchingEnabled = false   
}

Je déteste ce problème. Je passe 2 jours pour ce problème. Une référence sur Cell Pre-fetch @iOS 1 .


iOS 9 et avant ...

@ Away Lin a raison. . Je résous le même problème en implémentant cette méthode de délégué.

Ma Custom UICollectionViewLayout modifiera les attributs dans layoutAttributesForElementsInRect. La position de la section est dynamique et non statique. Ainsi, j'obtiens des avertissements sur le layout attributes for supplementary item at index path ... changed from ... to .... Avant les modifications, les méthodes liées à invalideLayout doivent être appelées.

Et, après avoir implémenté cette méthode déléguée pour renvoyer true, la méthode invalidateLayoutWithContext: sera appelé lors du défilement de UICollectionViewLayout. Par défaut, il renvoie false.

- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

De Apple Docs

Valeur renvoyée true si la vue de collection nécessite une mise à jour de la mise en page ou false si la mise en page n'a pas besoin d'être modifiée.

Discussion L'implémentation par défaut de cette méthode renvoie false. Les sous-classes peuvent la remplacer et renvoyer une valeur appropriée selon que les changements dans les limites de la vue de collection nécessitent des modifications de la disposition des cellules et des vues supplémentaires.

Si les limites de la vue de collection changent et que cette méthode retourne true, la vue de collection invalide la disposition en appelant la méthode invalidateLayoutWithContext:.

Disponibilité Disponible dans iOS 6.0 et versions ultérieures.


Et plus ...

n joli exemple de projet sur GitHub, pour UICollectionViewLayout personnalisé.

62
AechoLiu

J'ai eu la même exception: dans iOS 7, vous devez maintenant remplacer le isEqual: dans votre sous-classe UICollectionViewLayoutAttributes comme indiqué dans Apple ici .

11
Spi

J'ai résolu mon problème en remplaçant la méthode à la sous-classe de UICollectionViewFlowLayout:

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound

retour OUI

4
Away Lin

Je ne suis pas tout à fait sûr de savoir comment ou pourquoi, mais cela semble être résolu dans iOS 12, prenant en charge à la fois le redimensionnement de la vue supplémentaire et la prélecture. L'astuce pour moi était de m'assurer que les choses se passent dans le bon ordre.

Voici une implémentation fonctionnelle d'une vue d'en-tête extensible. Notez l'implémentation du redimensionnement de l'en-tête dans layoutAttributesForElements(in rect: CGRect):

class StretchyHeaderLayout: UICollectionViewFlowLayout {

    var cache = [UICollectionViewLayoutAttributes]()

    override func prepare() {
        super.prepare()

        cache.removeAll()

        guard let collectionView = collectionView else { return }

        let sections = [Int](0..<collectionView.numberOfSections)
        for section in sections {
            let items = [Int](0..<collectionView.numberOfItems(inSection: section))
            for item in items {
                let indexPath = IndexPath(item: item, section: section)
                if let attribute = layoutAttributesForItem(at: indexPath) {
                    cache.append(attribute)
                }
            }
        }

        if let header = layoutAttributesForSupplementaryView(ofKind: StretchyCollectionHeaderKind, at: IndexPath(item: 0, section: 0)) {
            cache.append(header)
        }
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        let visibleAttributes = cache.filter { rect.contains($0.frame) || rect.intersects($0.frame) }

        guard let collectionView = collectionView else { return visibleAttributes }

        // Find the header and stretch it while scrolling.
        guard let header = visibleAttributes.filter({ $0.representedElementKind == StretchyCollectionHeaderKind }).first else { return visibleAttributes }
        header.frame.Origin.y = collectionView.contentOffset.y
        header.frame.size.height = headerHeight.home - collectionView.contentOffset.y
        header.frame.size.width = collectionView.frame.size.width

        return visibleAttributes
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItem(at: indexPath as IndexPath)?.copy() as! UICollectionViewLayoutAttributes
        guard collectionView != nil else { return attributes }

        attributes.frame.Origin.y =  headerHeight.home + attributes.frame.Origin.y

        return attributes
    }

    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: StretchyCollectionHeaderKind, with: indexPath)
    }

    override var collectionViewContentSize: CGSize {
        get {
            guard let collectionView = collectionView else { return .zero }

            let numberOfSections = collectionView.numberOfSections
            let lastSection = numberOfSections - 1
            let numberOfItems = collectionView.numberOfItems(inSection: lastSection)
            let lastItem = numberOfItems - 1

            guard let lastCell = layoutAttributesForItem(at: IndexPath(item: lastItem, section: lastSection)) else { return .zero }

            return CGSize(width: collectionView.frame.width, height: lastCell.frame.maxY + sectionInset.bottom)
        }
    }
}

P.S .: Je sais que le cache ne sert en fait à rien à ce stade :)

2
brandonscript

J'ai eu ce problème aussi, car j'avais du code qui dépendait de la taille du contenu de la vue de collection. Mon code accédait à la taille du contenu via le collectionView!.contentSize au lieu de collectionViewContentSize.

Le premier utilise la propriété collectionView de UICollectionViewLayout, tandis que le second utilise la propriété layout implémentée sur mesure. Dans mon code, la première fois que l'on a demandé des attributs à la mise en page, contentSize n'avait pas encore été défini.

1
JSquared

Sélectionnez l'inspecteur d'attributs CollectionView et Goto. Décochez la case à cocher Prefetching Enabled . Ceci est résolu mon problème. Capture d'écran

0