Excepción de UICollectionView en UICollectionViewLayoutAttributes de iOS7


He hecho una vista en collectionView con CustomLayout. En iOS6 funcionó muy bien, pero iOS7 lanza una excepción como esta.

Terminación de la aplicación debido a la excepción no capturada 'NSInternalInconsistencyException', razón:

'atributos de diseño para elemento suplementario en la ruta del índice ({length = 2, path = 0 - 0}) cambiado de CustomSupplementaryAttributes: 0xd1123a0 ruta del índice: (NSIndexPath: 0xd112580 {length = 2, path = 0-0}); tipo de elemento: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; to CustomSupplementaryAttributes: 0xd583c80 index path: (NSIndexPath: 0xd583c70 {length = 2, path = 0 - 0}); element kind: (identifier); frame = (0 0; 1135.66 45); zIndex = -1; without invalidating the layout'

Author: Jirune, 2013-10-06

6 answers

Debe invalidar el diseño existente antes de actualizar, consulte el final del mensaje de error:

Sin invalidar el esquema "

[collectionViewLayout invalidateLayout];

Documentación de Apple para UICollectionViewLayout

 17
Author: Tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-22 15:55:39

IOS 10

En iOS 10, se introduce una nueva característica, es Cell Prefetching. Permitirá que la posición dinámica de SupplementaryView se bloquee. Para ejecutar el comportamiento anterior, necesita deshabilitar prefetchingEnabled. Es true por defecto en 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   
}

Odio este problema. Me paso 2 días para este problema. Una referencia sobre Cell Pre-fetch @iOS 10.


IOS 9 y anteriores ...

@Away Lin tiene razón.. Resuelvo el mismo problema implementando ese delegado método.

Mi Custom UICollectionViewLayout modificará los atributos layoutAttributesForElementsInRect. La posición de la sección es dinámica, no estática. Por lo tanto, obtengo advertencias sobre el layout attributes for supplementary item at index path ... changed from ... to .... Antes de los cambios, invalideLayout se deben llamar los métodos relacionados.

Y, después de implementar este método delegado para devolver true, se llamará al método invalidateLayoutWithContext: cuando se desplace el UICollectionViewLayout. Por defecto, devuelve false.

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

Desde Apple Docs

Valor devuelto true si la vista de colección requiere una actualización de diseño o false si el el diseño no necesita cambiar.

Discusión La implementación predeterminada de este método devuelve false. Las subclases pueden sobrescribirlo y devolver un valor apropiado basado en si los cambios en los límites de la vista de colección requieren cambios al diseño de celdas y vistas suplementarias.

Si los límites de la vista de colección cambian y este método devuelve true, la vista de colección invalida el diseño llamando a el invalidateLayoutWithContext: método.

Disponibilidad Disponible en iOS 6.0 y versiones posteriores.


Y más ...

Un buen proyecto de ejemplo en GitHub, para la costumbre UICollectionViewLayout.

 54
Author: AechoLiu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:23

Tuve la misma excepción: en iOS 7, ahora necesita reemplazar la subclase heredada isEqual: en su UICollectionViewLayoutAttributes como se indica en la documentación de Apple aquí.

 9
Author: Spi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-09 22:31:00

Resolví mi problema anulando el método en la subclase de UICollectionViewFlowLayout:

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound

Return YES

 4
Author: Away Lin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-30 07:39:09

También tuve este problema, porque tenía código que dependía del tamaño del contenido de la vista de colección. Mi código accedía al tamaño del contenido a través de collectionView!.contentSize en lugar de collectionViewContentSize.

El primero usa la propiedad collectionView de UICollectionViewLayout, mientras que el segundo usa la propiedad layout implementada a medida. En mi código, la primera vez que se le pidió atributos al diseño, contentSize aún no se había establecido.

 0
Author: JSquared,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-02 23:42:32

No estoy completamente seguro de cómo o por qué, pero esto parece estar arreglado en iOS 12, soportando tanto el redimensionamiento de vistas suplementarias como la precolocación. El truco para mí era asegurarme de que las cosas estaban sucediendo en el orden correcto.

Aquí está una implementación de trabajo de una vista de encabezado extensible. Observe la implementación del cambio de tamaño del encabezado que ocurre en 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.d.: Soy consciente de que la caché en realidad no tiene ningún propósito en este punto:)

 0
Author: brandonscript,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-28 22:52:23