Pregunta UICollectionView flowLayout no ajusta las celdas correctamente (iOS)


tengo un UICollectionView con un FLowLayout. Funcionará como espero la mayoría de las veces, pero de vez en cuando una de las celdas no se ajusta correctamente. Por ejemplo, la celda que debería estar activada en la primera "columna" de la tercera fila si en realidad está detrás de la segunda fila y solo hay un espacio vacío donde debería estar (ver el diagrama a continuación). Todo lo que puedes ver de esta celda de colorete es el lado izquierdo (el resto está cortado) y el lugar donde debería estar está vacío.

Esto no sucede de manera consistente; no es siempre la misma fila Una vez que ha sucedido, puedo desplazarme hacia arriba y luego hacia atrás y la celda se habrá arreglado por sí misma. O bien, cuando presiono la celda (que me lleva a la siguiente vista mediante un impulso) y luego regreso, veré la celda en la posición incorrecta y luego saltará a la posición correcta.

La velocidad de desplazamiento parece facilitar la reproducción del problema. Cuando me desplazo lentamente, todavía puedo ver la celda en la posición incorrecta de vez en cuando, pero luego saltará a la posición correcta de inmediato.

El problema comenzó cuando agregué las secciones insertadas. Anteriormente, tenía las celdas casi al ras contra los límites de la colección (poco o ningún inserto) y no noté el problema. Pero esto significaba que la vista derecha e izquierda de la colección estaba vacía. Es decir, no podría desplazarse. Además, la barra de desplazamiento no estaba alineada a la derecha.

Puedo hacer que el problema ocurra tanto en el simulador como en un iPad 3.

Supongo que el problema está sucediendo debido a las secciones de las secciones izquierda y derecha ... Pero si el valor es incorrecto, entonces esperaría que el comportamiento sea consistente. Me pregunto si esto podría ser un error con Apple. O tal vez esto se deba a una acumulación de las inserciones o algo similar ...

Cualquier solución / solución alternativa es apreciada.

Illustration of problem and settings


Seguir: He estado usando esta respuesta debajo por Nick para más 6 meses  12 meses 2 años ahora sin problemas (en caso de que las personas se pregunten si hay agujeros en esa respuesta, todavía no he encontrado ninguno). Bien hecho Nick.


75
2017-10-17 04:18


origen


Respuestas:


Hay un error en la implementación de UICollectionViewFlowLayout de layoutAttributesForElementsInRect que hace que devuelva DOS objetos de atributo para una sola celda en ciertos casos que involucran secciones insertadas. Uno de los objetos de atributo devueltos no es válido (fuera de los límites de la vista de colección) y el otro es válido. A continuación se muestra una subclase de UICollectionViewFlowLayout que soluciona el problema al excluir celdas fuera de los límites de la vista de colección.

// NDCollectionViewFlowLayout.h
@interface NDCollectionViewFlowLayout : UICollectionViewFlowLayout
@end

// NDCollectionViewFlowLayout.m
#import "NDCollectionViewFlowLayout.h"
@implementation NDCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
  NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count];
  for (UICollectionViewLayoutAttributes *attribute in attributes) {
    if ((attribute.frame.origin.x + attribute.frame.size.width <= self.collectionViewContentSize.width) &&
        (attribute.frame.origin.y + attribute.frame.size.height <= self.collectionViewContentSize.height)) {
      [newAttributes addObject:attribute];
    }
  }
  return newAttributes;
}
@end

Ver esta.

Otras respuestas sugieren que se devuelva SÍ desde shouldInvalidateLayoutForBoundsChange, pero esto provoca cálculos innecesarios y ni siquiera resuelve completamente el problema.

Mi solución resuelve completamente el error y no debería causar ningún problema cuando Apple corrige la causa raíz.


91
2017-11-14 23:55



descubrí problemas similares en mi aplicación de iPhone. La búsqueda en el foro de desarrollo de Apple me trajo esta solución adecuada que funcionó en mi caso y probablemente también en tu caso:

Subclase UICollectionViewFlowLayout y anular shouldInvalidateLayoutForBoundsChange regresar YES.

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

y

//.m
#import "MainLayout.h"
@implementation MainLayout
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}
@end

7
2017-10-31 20:06



Pon esto en viewController que posee la vista de colección

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    [self.collectionView.collectionViewLayout invalidateLayout];
}

7
2018-06-03 08:48



Una versión Swift de la respuesta de Nick Snyder:

class NDCollectionViewFlowLayout : UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)
        let contentSize = collectionViewContentSize
        return attributes?.filter { $0.frame.maxX <= contentSize.width && $0.frame.maxY < contentSize.height }
    }
}

7
2017-07-03 12:06



He tenido este problema también para un diseño básico de gridview con inserciones para los márgenes. La depuración limitada que he hecho por el momento está implementando - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect en mi subclase UICollectionViewFlowLayout y registrando lo que devuelve la implementación de superclase, que muestra claramente el problema.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attrsList = [super layoutAttributesForElementsInRect:rect];

    for (UICollectionViewLayoutAttributes *attrs in attrsList) {
        NSLog(@"%f %f", attrs.frame.origin.x, attrs.frame.origin.y);
    }

    return attrsList;
}

Implementando - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath También puedo ver que parece devolver los valores incorrectos para itemIndexPath.item == 30, que es el factor 10 del número de celdas de mi gridview por línea, no estoy seguro si eso es relevante.

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    UICollectionViewLayoutAttributes *attrs = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

    NSLog(@"initialAttrs: %f %f atIndexPath: %d", attrs.frame.origin.x, attrs.frame.origin.y, itemIndexPath.item);

    return attrs;
}

Debido a la falta de tiempo para realizar más depuraciones, la solución que he hecho por ahora reduce mi ancho de vistas de colección con una cantidad igual al margen izquierdo y derecho. Tengo un encabezado que todavía necesita el ancho completo, así que configuré clipsToBounds = NO en mi colección y luego también eliminé las inserciones izquierda y derecha, parece que funciona. Para que la vista de encabezado permanezca en su lugar, debe implementar el cambio de marco y el tamaño en los métodos de diseño que tienen la tarea de devolver los atributos de diseño para la vista de encabezado.


5
2017-10-17 09:28



He agregado un informe de error a Apple. Lo que funciona para mí es establecer la sección inferior del objeto en un valor inferior al recuadro superior.


4
2017-11-08 13:26



Estaba experimentando el mismo problema de eliminación de células en el iPhone usando un UICollectionViewFlowLayout y me alegré de encontrar tu publicación. Sé que está teniendo el problema en un iPad, pero estoy publicando esto porque creo que es un problema general con el UICollectionView. Entonces esto es lo que descubrí.

Puedo confirmar que el sectionInset es relevante para ese problema. Además de eso, headerReferenceSize también tiene influencia si una célula se desplaza o no. (Esto tiene sentido ya que es necesario para calcular el origen).

Desafortunadamente, incluso los diferentes tamaños de pantalla deben tenerse en cuenta. Al jugar con los valores de estas dos propiedades, experimenté que cierta configuración funcionaba tanto en ambos (3.5 "como en 4"), en ninguno, o en solo uno de los tamaños de pantalla. Usualmente ninguno de ellos. (Esto también tiene sentido, ya que los límites de la UICollectionView cambios, por lo tanto, no experimenté ninguna disparidad entre la retina y la no retina).

Terminé configurando el sectionInset y headerReferenceSize dependiendo del tamaño de la pantalla. Intenté cerca de 50 combinaciones hasta que encontré valores bajo los cuales el problema ya no ocurría y el diseño era visualmente aceptable. Es muy difícil encontrar valores que funcionen en ambos tamaños de pantalla.

Resumiendo, solo puedo recomendarte que juegues con los valores, verifícalos en diferentes tamaños de pantalla y espero que Apple solucione este problema.


3
2017-10-29 17:50



Acabo de encontrar un problema similar con la desaparición de las celdas después de que UICollectionView scroll en iOS 10 (no tengo problemas en iOS 6-9).

Subclases de UICollectionViewFlowLayout y método de sobreescalar layoutAttributesForElementsInRect: no funciona en mi caso.

La solución fue bastante simple. Actualmente uso una instancia de UICollectionViewFlowLayout y establezco tanto itemSize como estimatedItemSize (No usé estimateItemSize antes) y lo configuré en un tamaño distinto de cero. El tamaño real se calcula en collectionView: layout: sizeForItemAtIndexPath: method.

Además, eliminé una llamada del método invalidateLayout de layoutSubviews para evitar recargas innecesarias.


3
2017-10-31 09:44