2012-09-26 59 views
58

Me estoy poniendo este error en la realización de insertItemsAtIndexPaths en UICollectionViewfracaso UICollectionView aserción

error de aserción en:

-[UICollectionViewData indexPathForItemAtGlobalIndex:], 
/SourceCache/UIKit/UIKit-2372/UICollectionViewData.m:442 
2012-09-26 18:12:34.432 
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', 
reason: 'request for index path for global index 805306367 
when there are only 1 items in the collection view' 

He comprobado y mi fuente de datos contiene sólo un elemento. ¿Alguna idea de por qué esto podría pasar? Si se necesita más información, definitivamente puedo proporcionar eso.

Respuesta

67

me encontré con este mismo problema cuando la inserción de la primera celda en una vista de colección . He arreglado el problema cambiando mi código para que yo llamo el método UICollectionView

- (void)reloadData 

al insertar la primera celda, pero

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths 

al insertar todas las demás células.

Curiosamente, también tenía un problema con

- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths 

al borrar la última celda. Hice lo mismo que antes: solo llame al reloadData cuando borre la última celda.

+14

Esta parece ser la respuesta correcta. El bloqueo ocurre si está insertando la primera celda. Hay un radar abierto sobre este problema: http://openradar.appspot.com/12954582, que dice que el problema solo ocurre si está usando 'vistas decoradas' (encabezados o pies de página de las secciones). – chris

+2

Vistas suplementarias y el uso de reloadData en el primer elemento y insertItemsAtIndexPaths en los siguientes elementos, no funciona para mí. Sin embargo, si deshabilito las vistas suplementarias, entonces funciona. Y si solo uso reloadData, funciona. Tristemente, me gustaría usar -insertItemsAtIndexPaths con mi código KVO. – neoneye

+0

para ampliar esta respuesta, si llamo 'reloadData' fuera y antes del bloque' performBatchUpdates: ', la interfaz de usuario se actualiza según lo deseado y el bloqueo desaparece. – toblerpwn

1

Compruebe que está devolviendo el número correcto de elementos en los métodos UICollectionViewDataSource:

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 

y

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 
+0

Yup acaba de regresar 1. Al parecer, este problema solo ocurre al insertar el primer elemento. Y no es un problema a partir de ese momento. Pero, en cambio, cuando probé la vista de colección "reloadData" en el primer elemento, no hay problema. – jajo87

11

Insertar la sección # 0 justo antes de insertar celdas parece hacer feliz a UICollectionView.

NSArray *indexPaths = /* indexPaths of the cells to be inserted */ 
NSUInteger countBeforeInsert = _cells.count; 
dispatch_block_t updates = ^{ 
    if (countBeforeInsert < 1) { 
     [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:0]]; 
    } 
    [self.collectionView insertItemsAtIndexPaths:indexPaths]; 
}; 
[self.collectionView performBatchUpdates:updates completion:nil]; 
+0

Esto parece funcionar para mí, también estoy usando una vista suplementaria para la sección –

+4

esto no funcionó para mí :( – haider

+1

Para que esto funcione, tuve que modificar el valor de retorno de 'numberOfSectionsInCollectionView:' Método Devuelve 0 secciones si su colección está vacía y devuelve 1 si la colección no lo está.Si no está implementando este método o devolviendo 0, obtendrá errores de "actualización no válida" quejándose de la cantidad de secciones que no están correctas después de hacer la inserción. – nrj

1

Me encontré con este problema también. Esto es lo que me pasó a mí:

  1. I subclases UICollectionViewController y, por initWithCollectionViewLayout:, estaba inicializando mi NSFetchedResultsController.
  2. tener una clase compartida buscar a los resultados de un NSURLConnection y analizar la cadena JSON (subproceso diferente)
  3. bucle a través de la alimentación y crear mi NSManagedObjects, añadirlos a mi NSManagedObjectContext, que tiene de NSManagedObjectContext el hilo principal como parentContext.
  4. Guarde mi contexto.
  5. Haga que mi NSFetchedResultsController recoja los cambios y los ponga en cola.
  6. En - (void)controllerDidChangeContent:, procesaría los cambios y los aplicaría a mi UICollectionView.

De forma intermitente, obtengo el error que está recibiendo el OP y no pude entender por qué.

Para solucionar este problema, moví la inicialización NSFetchedResultsController y performFetch a mi método - viewDidLoad y este problema ya no existe.No es necesario llamar al [collectionView reloadData] o cualquier cosa y todas las animaciones funcionan correctamente.

Espero que esto ayude!

1

Parece que el problema se produce cuando inserta o mueve una celda a una sección que contiene una vista adicional de encabezado o pie de página (con UICollectionViewFlowLayout o un diseño derivado de eso) y la sección tiene un recuento de 0 celdas antes del inserción/movimiento.

sólo podía eludir el accidente y aún así mantener las animaciones por tener una celda vacía e invisible en la sección que contiene la vista de encabezado adicional como esto:

  1. Hacer - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section devolver el celular real `count + 1 para esa sección donde está la vista del encabezado.
  2. En - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath retorno

    if ((indexPath.section == YOUR_SECTION_WITH_THE_HEADER_VIEW) && (indexPath.item == [self collectionView:collectionView numberOfItemsInSection:indexPath.section] - 1)) { 
         cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"EmptyCell" forIndexPath:indexPath]; 
    } 
    

    ... una celda vacía para esa posición. Recuerde que debe registrar la reutilización celular en viewDidLoad o dondequiera que usted hace funcionar su UICollectionView:

    [self.collectionView registerClass:[UICollectionReusableView class] forCellWithReuseIdentifier:@"EmptyCell"]; 
    
  3. moveItemAtIndexPath: uso o insertItemsAtIndexPaths: sin que se caiga.
+1

Tengo este problema sin ninguna vista suplementaria. – kelin

0

Solo para el registro, me encontré con el mismo problema y para mí la solución fue eliminar el encabezado (deshabilitarlos en .xib) y como ya no se necesitaban más eliminaron este método. Después de eso todo parece estar bien.

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementary 
ElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 
3

Mi vista de colección consistía en obtener elementos de dos fuentes de datos y su actualización ocasionó este problema. Mi solución era hacer cola la actualización de los datos y la recogida de vista de recarga juntos:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{ 

       //Update Data Array 
       weakSelf.dataProfile = [results lastObject]; 

       //Reload CollectionView 
       [weakSelf.collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]]; 
}]; 
5

He publicado un trabajo en torno a este tema aquí: https://gist.github.com/iwasrobbed/5528897

En la categoría privada en la parte superior de su archivo .m:

@interface MyViewController() 
{ 
    BOOL shouldReloadCollectionView; 
    NSBlockOperation *blockOperation; 
} 
@end 

A continuación, sus devoluciones de llamada de delegados serían:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{ 
    shouldReloadCollectionView = NO; 
    blockOperation = [NSBlockOperation new]; 
} 

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo 
      atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type 
{ 
    __weak UICollectionView *collectionView = self.collectionView; 
    switch (type) { 
     case NSFetchedResultsChangeInsert: { 
      [blockOperation addExecutionBlock:^{ 
       [collectionView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; 
      }]; 
      break; 
     } 

     case NSFetchedResultsChangeDelete: { 
      [blockOperation addExecutionBlock:^{ 
       [collectionView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; 
      }]; 
      break; 
     } 

     case NSFetchedResultsChangeUpdate: { 
      [blockOperation addExecutionBlock:^{ 
       [collectionView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; 
      }]; 
      break; 
     } 

     default: 
      break; 
    } 
} 

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath 
{ 
    __weak UICollectionView *collectionView = self.collectionView; 
    switch (type) { 
     case NSFetchedResultsChangeInsert: { 
      if ([self.collectionView numberOfSections] > 0) { 
       if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) { 
        shouldReloadCollectionView = YES; 
       } else { 
        [blockOperation addExecutionBlock:^{ 
         [collectionView insertItemsAtIndexPaths:@[newIndexPath]]; 
        }]; 
       } 
      } else { 
       shouldReloadCollectionView = YES; 
      } 
      break; 
     } 

     case NSFetchedResultsChangeDelete: { 
      if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) { 
       shouldReloadCollectionView = YES; 
      } else { 
       [blockOperation addExecutionBlock:^{ 
        [collectionView deleteItemsAtIndexPaths:@[indexPath]]; 
       }]; 
      } 
      break; 
     } 

     case NSFetchedResultsChangeUpdate: { 
      [blockOperation addExecutionBlock:^{ 
       [collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
      }]; 
      break; 
     } 

     case NSFetchedResultsChangeMove: { 
      [blockOperation addExecutionBlock:^{ 
       [collectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; 
      }]; 
      break; 
     } 

     default: 
      break; 
    } 
} 

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{ 
    // Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582 
    if (shouldReloadCollectionView) { 
     [self.collectionView reloadData]; 
    } else { 
     [self.collectionView performBatchUpdates:^{ 
      [blockOperation start]; 
     } completion:nil]; 
    } 
} 

El crédito por este enfoque es para Blake Watters.

+1

Encontré el enfoque de Blake aquí: https://gist.github.com/Lucien/4440c1cba83318e276bb pero no pude hacerlo funcionar debido a la falla (creo). Así que muchas gracias por tomarse el tiempo para publicar esta versión completa. ¡Funciona muy bien! – PJC

+0

Gracias. Sin embargo, parece que la cláusula if en 'NSFetchedResultsChangeInsert' debería verificar el número de elementos en la sección' * new * IndexPath.section'. –

+0

Usar NSBlockOperation de esta manera es peligroso, ya que no garantiza que se invocarán los bloques añadidos en el hilo principal. Ver mi comentario [aquí] (https://gist.github.com/Lucien/4440c1cba83318e276bb#comment-1184323) para más detalles. –

0

Me tocó este problema yo mismo. Todas las respuestas aquí parecían presentar problemas, excepto Alex L's. Referir la actualización parecía ser la respuesta. Aquí está mi solución final:

- (void)addItem:(id)item { 
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
     if (!_data) { 
      _data = [NSMutableArray arrayWithObject:item]; 
     } else { 
      [_data addObject:item]; 
     } 
     [_collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:_data.count-1 inSection:0]]]; 
    }]; 
} 
0

La solución que realmente funciona es devolver una altura de 0 si la celda en la ruta del índice de su punto de vista complementario no está allí (carga inicial, que ha eliminado la fila, etc.) . Véase mi respuesta aquí:

https://stackoverflow.com/a/18411860/917104

4

Aquí es un no-corte, de respuesta basados ​​en documentos al problema. En mi caso, había una condición según la cual devolvería una vista válida o nula suplementaria desde collectionView:viewForSupplementaryElementOfKind:atIndexPath:.Después de encontrar el bloqueo, revisé los documentos y esto es lo que dicen:

Este método siempre debe devolver un objeto de vista válido. Si no desea una vista suplementaria en un caso particular, su objeto de disposición debe no crear los atributos para esa vista. Alternativamente, puede ocultar visualizando la propiedad oculta de los atributos correspondientes en YES o estableciendo la propiedad alfa de los atributos en 0. Para ocultar vistas de encabezado y pie de página en un diseño de flujo, también puede establecer el ancho y altura de esos puntos de vista a 0.

Hay otras maneras de hacer esto, pero la más rápida parece ser:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { 
    return <condition> ? collectionViewLayout.headerReferenceSize : CGSizeZero; 
} 
+0

esta debería ser la respuesta correcta, ¡y no es un error de Apple! – scorpiozj

1

Aquí hay una solución para este error que he estado usando en mis proyectos pensé en publicar aquí en caso de que alguien lo encontrara valioso.

@interface FetchedResultsViewController() 

@property (nonatomic) NSMutableIndexSet *sectionsToAdd; 
@property (nonatomic) NSMutableIndexSet *sectionsToDelete; 

@property (nonatomic) NSMutableArray *indexPathsToAdd; 
@property (nonatomic) NSMutableArray *indexPathsToDelete; 
@property (nonatomic) NSMutableArray *indexPathsToUpdate; 

@end 
#pragma mark - NSFetchedResultsControllerDelegate 


- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{ 
    [self resetFetchedResultControllerChanges]; 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo 
      atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type 
{ 
    switch(type) 
    { 
     case NSFetchedResultsChangeInsert: 
      [self.sectionsToAdd addIndex:sectionIndex]; 
      break; 

     case NSFetchedResultsChangeDelete: 
      [self.sectionsToDelete addIndex:sectionIndex]; 
      break; 
    } 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject 
     atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type 
     newIndexPath:(NSIndexPath *)newIndexPath 
{ 
    switch(type) 
    { 
     case NSFetchedResultsChangeInsert: 
      [self.indexPathsToAdd addObject:newIndexPath]; 
      break; 

     case NSFetchedResultsChangeDelete: 
      [self.indexPathsToDelete addObject:indexPath]; 
      break; 

     case NSFetchedResultsChangeUpdate: 
      [self.indexPathsToUpdate addObject:indexPath]; 
      break; 

     case NSFetchedResultsChangeMove: 
      [self.indexPathsToAdd addObject:newIndexPath]; 
      [self.indexPathsToDelete addObject:indexPath]; 
      break; 
    } 
} 


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{ 
    if (self.sectionsToAdd.count > 0 || self.sectionsToDelete.count > 0 || self.indexPathsToAdd.count > 0 || self.indexPathsToDelete > 0 || self.indexPathsToUpdate > 0) 
    { 
     if ([self shouldReloadCollectionViewFromChangedContent]) 
     { 
      [self.collectionView reloadData]; 

      [self resetFetchedResultControllerChanges]; 
     } 
     else 
     { 
      [self.collectionView performBatchUpdates:^{ 

       if (self.sectionsToAdd.count > 0) 
       { 
        [self.collectionView insertSections:self.sectionsToAdd]; 
       } 

       if (self.sectionsToDelete.count > 0) 
       { 
        [self.collectionView deleteSections:self.sectionsToDelete]; 
       } 

       if (self.indexPathsToAdd.count > 0) 
       { 
        [self.collectionView insertItemsAtIndexPaths:self.indexPathsToAdd]; 
       } 

       if (self.indexPathsToDelete.count > 0) 
       { 
        [self.collectionView deleteItemsAtIndexPaths:self.indexPathsToDelete]; 
       } 

       for (NSIndexPath *indexPath in self.indexPathsToUpdate) 
       { 
        [self configureCell:[self.collectionView cellForItemAtIndexPath:indexPath] 
          atIndexPath:indexPath]; 
       } 

      } completion:^(BOOL finished) { 
       [self resetFetchedResultControllerChanges]; 
      }]; 
     } 
    } 
} 

// This is to prevent a bug in UICollectionView from occurring. 
// The bug presents itself when inserting the first object or deleting the last object in a collection view. 
// http://stackoverflow.com/questions/12611292/uicollectionview-assertion-failure 
// This code should be removed once the bug has been fixed, it is tracked in OpenRadar 
// http://openradar.appspot.com/12954582 
- (BOOL)shouldReloadCollectionViewFromChangedContent 
{ 
    NSInteger totalNumberOfIndexPaths = 0; 
    for (NSInteger i = 0; i < self.collectionView.numberOfSections; i++) 
    { 
     totalNumberOfIndexPaths += [self.collectionView numberOfItemsInSection:i]; 
    } 

    NSInteger numberOfItemsAfterUpdates = totalNumberOfIndexPaths; 
    numberOfItemsAfterUpdates += self.indexPathsToAdd.count; 
    numberOfItemsAfterUpdates -= self.indexPathsToDelete.count; 

    BOOL shouldReload = NO; 
    if (numberOfItemsAfterUpdates == 0 && totalNumberOfIndexPaths == 1) 
    { 
     shouldReload = YES; 
    } 

    if (numberOfItemsAfterUpdates == 1 && totalNumberOfIndexPaths == 0) 
    { 
     shouldReload = YES; 
    } 

    return shouldReload; 
} 

- (void)resetFetchedResultControllerChanges 
{ 
    [self.sectionsToAdd removeAllIndexes]; 
    [self.sectionsToDelete removeAllIndexes]; 
    [self.indexPathsToAdd removeAllObjects]; 
    [self.indexPathsToDelete removeAllObjects]; 
    [self.indexPathsToUpdate removeAllObjects]; 
} 
1

En mi caso, el problema era la forma en que estaba creando mi NSIndexPath. Por ejemplo, para borrar la tercera celda, en lugar de hacer:

NSIndexPath* indexPath = [NSIndexPath indexPathWithIndex:2]; 
[_collectionView deleteItemsAtIndexPaths:@[indexPath]]; 

que tenía que hacer:

NSIndexPath* indexPath = [NSIndexPath indexPathForItem:2 inSection:0]; 
[_collectionView deleteItemsAtIndexPaths:@[indexPath]]; 
1

verificar que se está devolviendo el valor correcto en numberOfSectionsInCollectionView:

El valor I estaba usando para calcular secciones era nulo, por lo tanto 0 secciones. Esto causó la excepción.

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 
    NSInteger sectionCount = self.objectThatShouldHaveAValueButIsActuallyNil.sectionCount; 

    // section count is wrong! 

    return sectionCount; 
}