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 diagtwig a continuación). Todo lo que puedes ver de esta celda de colorete es el lado izquierdo (el rest 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.

Ilustración del problema y la configuración


Seguimiento : He estado usando esta respuesta a continuación por Nick durante más de 6 meses, 12 meses y 2 años sin ningún problema (en caso de que las personas se pregunten si hay agujeros en esa respuesta, todavía no he encontrado ninguno). Bien hecho Nick.

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 

Mira esto

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.

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:

La subclase UICollectionViewFlowLayout y anula shouldInvalidateLayoutForBoundsChange para devolver YES .

 //.h @interface MainLayout : UICollectionViewFlowLayout @end 

y

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

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

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

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 } } } 

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 la súper clase, lo 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.

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.

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

Puedo confirmar que sectionInset es relevante para ese problema. Además, el headerReferenceSize también tiene influencia sobre si una celda 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 UICollectionView cambian, por lo tanto, no experimenté ninguna disparidad entre la retina y la no retina).

Terminé configurando el sectionInset y el 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.

Acabo de encontrar un problema similar con la desaparición de las células después de UICollectionView scroll en iOS 10 (no tuve 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 utilicé 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.

Acabo de experimentar un problema similar, pero encontré una solución muy diferente.

Estoy usando una implementación personalizada de UICollectionViewFlowLayout con un desplazamiento horizontal. También estoy creando ubicaciones de marcos personalizados para cada celda.

El problema que tenía era que [super layoutAttributesForElementsInRect: rect] no devolvía todos los UICollectionViewLayoutAttributes que deberían mostrarse en la pantalla. En las llamadas a [self.collectionView reloadData], algunas de las celdas se configuraron de repente como ocultas.

Lo que terminé haciendo fue crear un NSMutableDictionary que almacenaba todos los UICollectionViewLayoutAttributes que había visto hasta el momento y luego incluía todos los elementos que sé que deberían mostrarse.

 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect]; NSMutableArray * attrs = [NSMutableArray array]; CGSize calculatedSize = [self calculatedItemSize]; [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]; [self.savedAttributesDict addAttribute:attr]; } }]; // We have to do this because there is a bug in the collection view where it won't correctly return all of the on screen cells. [self.savedAttributesDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray * cachedAttributes, BOOL *stop) { CGFloat columnX = [key floatValue]; CGFloat leftExtreme = columnX; // This is the left edge of the element (I'm using horizontal scrolling) CGFloat rightExtreme = columnX + calculatedSize.width; // This is the right edge of the element (I'm using horizontal scrolling) if (leftExtreme < = (rect.origin.x + rect.size.width) || rightExtreme >= rect.origin.x) { for (UICollectionViewLayoutAttributes * attr in cachedAttributes) { [attrs addObject:attr]; } } }]; return attrs; } 

Aquí está la categoría para NSMutableDictionary que UICollectionViewLayoutAttributes se está guardando correctamente.

 #import "NSMutableDictionary+CDBCollectionViewAttributesCache.h" @implementation NSMutableDictionary (CDBCollectionViewAttributesCache) - (void)addAttribute:(UICollectionViewLayoutAttributes*)attribute { NSString *key = [self keyForAttribute:attribute]; if (key) { if (![self objectForKey:key]) { NSMutableArray *array = [NSMutableArray new]; [array addObject:attribute]; [self setObject:array forKey:key]; } else { __block BOOL alreadyExists = NO; NSMutableArray *array = [self objectForKey:key]; [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *existingAttr, NSUInteger idx, BOOL *stop) { if ([existingAttr.indexPath compare:attribute.indexPath] == NSOrderedSame) { alreadyExists = YES; *stop = YES; } }]; if (!alreadyExists) { [array addObject:attribute]; } } } else { DDLogError(@"%@", [CDKError errorWithMessage:[NSString stringWithFormat:@"Invalid UICollectionVeiwLayoutAttributes passed to category extension"] code:CDKErrorInvalidParams]); } } - (NSArray*)attributesForColumn:(NSUInteger)column { return [self objectForKey:[NSString stringWithFormat:@"%ld", column]]; } - (void)removeAttributesForColumn:(NSUInteger)column { [self removeObjectForKey:[NSString stringWithFormat:@"%ld", column]]; } - (NSString*)keyForAttribute:(UICollectionViewLayoutAttributes*)attribute { if (attribute) { NSInteger column = (NSInteger)attribute.frame.origin.x; return [NSString stringWithFormat:@"%ld", column]; } return nil; } @end 

Las respuestas anteriores no funcionan para mí, pero después de descargar las imágenes, reemplacé

 [self.yourCollectionView reloadData] 

con

 [self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]]; 

para actualizar y puede mostrar todas las celdas correctamente, puedes intentarlo.

Esto puede ser un poco tarde, pero asegúrese de establecer sus atributos en prepare() si es posible.

Mi problema era que las celdas se estaban desplegando y obteniendo la actualización en layoutAttributesForElements . Esto dio lugar a un efecto de parpadeo cuando las nuevas células se pusieron a la vista.

Al mover toda la lógica de atributos a la prepare , al configurarlos en UICollectionViewCell.apply() se eliminó el parpadeo y se creó una celda lisa de mantequilla mostrando 😊