UICollectionView flowLayout no envuelve celdas correctamente (iOS)

Tengo una UICollectionView con un FLowLayout. Funcionará como espero la mayoría del time, pero de vez en cuando una de las celdas no se envuelve correctamente. Por ejemplo, la celda que debe estar encendida en la primera "columna" de la tercera fila si realmente se está arrastrando en 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 constante; no siempre es la misma fila. Una vez que ha sucedido, puedo desplazarme hacia arriba y luego hacia atrás y la celda se habrá arreglado. O bien, cuando presiono la celda (que me lleva a la siguiente vista a través de un push) y luego vuelvo a abrir, veré la celda en la position incorrecta y luego saltará a la position correcta.

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

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

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

Creo que el problema está sucediendo debido a las inserciones de la sección 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 inserciones o algo similar …

Se agradecen todas las soluciones / soluciones.

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 problemas (en caso de que las personas se pregunten si hay agujeros en esa respuesta, todavía no he encontrado ninguna). Bien hecho Nick.

Hay un error en la implementación de layoutAttributesForElementsInRect de UICollectionViewFlowLayout que hace que devuelva DOS objects de atributo para una sola celda en ciertos casos que involucran inserciones de sección. Uno de los objects 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 subclass de UICollectionViewFlowLayout que corrige el problema excluyendo 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 devolver YES de shouldInvalidateLayoutForBoundsChange, pero esto provoca recálculos innecesarios y ni siquiera resuelve completamente el problema.

Mi solución soluciona por completo 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. Buscando en el foro de Apple dev me trajo esta solución adecuada que funcionó en mi caso y probablemente también en su caso:

La subclass UICollectionViewFlowLayout y override shouldInvalidateLayoutForBoundsChange para devolver YES .

 //.h @interface MainLayout : UICollectionViewFlowLayout @end 

y

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

Ponga esto en el 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 } } } 

También tuve este problema para un layout de gridview básico con inserciones para márgenes. La debugging limitada que he hecho por ahora está implementando - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect en mi subclass UICollectionViewFlowLayout y al registrar lo que devuelve la implementación de súper class, 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; } 

Al implementar - (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; } 

Con la falta de time para una mayor debugging, la solución que he hecho por ahora se networkinguce el ancho de mi colección de vistas con una cantidad igual al margen izquierdo y derecho. Tengo un encabezado que aún necesita el ancho completo, así que he configurado clipsToBounds = NO en mi colección y luego también he eliminado las inserciones izquierda y derecha en él, parece funcionar. Para que la vista del encabezado permanezca en su lugar, debe implementar el cambio y el tamaño del marco en los methods de layout que tienen asignada la tarea de devolver el atributo de disposition para la vista del encabezado.

He agregado un informe de error a Apple. Lo que funciona para mí es configurar la parte inferior de la sección de inicio en un valor menor que la parte superior de la inserción.

Estaba experimentando el mismo problema de sustitución de células en el iPhone usando UICollectionViewFlowLayout así que 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 UICollectionView . Entonces esto es lo que descubrí.

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

Desafortunadamente, incluso se deben tener en count los diferentes tamaños de pantalla. Al jugar con los valores de estas dos properties, experimenté que una determinada configuration funcionaba tanto en (3,5 "y 4"), en ninguna, o en solo uno de los tamaños de pantalla. Por lo general, 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 sectionInset y headerReferenceSize según el tamaño de la pantalla. Probé alnetworkingedor de 50 combinaciones hasta que encontré valores bajo los cuales el problema ya no ocurría y el layout era visualmente aceptable. Es muy difícil encontrar valores que funcionen en ambos tamaños de pantalla.

Resumiendo, puedo recomendarte que juegues con los valores, compruébalos en diferentes tamaños de pantalla y espero que Apple solucione este problema.

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 frameworks personalizados para cada celda.

El problema que estaba teniendo era que [super layoutAttributesForElementsInRect: rect] no estaba devolviendo todos los UICollectionViewLayoutAttributes que deberían mostrarse en la pantalla. En las llamadas a [self.collectionView reloadData] algunas de las celdas de repente se establecerán en ocultas.

Lo que terminé haciendo fue crear un NSMutableDictionary que almacenaba en caching todos los UICollectionViewLayoutAttributes que he visto hasta ahora y luego include cualquier elemento que sé que debería 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 guarda 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] == NSOrdenetworkingSame) { 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 download las imágenes, reemplacé

 [self.yourCollectionView reloadData] 

con

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

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

Acabo de encontrar un problema similar con las células que desaparecen después de que UICollectionView se desplaza en iOS 10 (no tuvo problemas en iOS 6-9).

Subsorting de UICollectionViewFlowLayout y el método de anulación layoutAttributesForElementsInRect: no funciona en mi caso.

La solución fue bastante simple. Actualmente utilizo una instancia de UICollectionViewFlowLayout y establezco both itemSize y estimatedItemSize (no utilicé estimatedItemSize antes) y lo establezco en un tamaño distinto de cero. El tamaño real se calcula en collectionView: layout: sizeForItemAtIndexPath: method.

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