2011-02-01 15 views
21

Tengo un NSFetchedResultsController para actualizar una UITableView con contenido de Core Data. Es algo bastante estándar, estoy seguro de que todos lo han visto muchas veces, sin embargo, me estoy encontrando con un pequeño problema. En primer lugar aquí es mi código:NSFetchedResultsController ignora fetchLimit?

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Article" inManagedObjectContext:self.managedObjectContext]; 

[fetchRequest setEntity:entity]; 

[fetchRequest setFetchLimit:20]; 

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(folder.hidden == NO)"]; 
[fetchRequest setPredicate:predicate]; 

NSSortDescriptor *sort1 = [NSSortDescriptor sortDescriptorWithKey:@"sortDate" ascending:NO]; 
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sort1, nil]]; 

NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] 
     initWithFetchRequest:fetchRequest 
     managedObjectContext:self.managedObjectContext 
     sectionNameKeyPath:nil 
     cacheName:nil]; 
[fetchRequest release]; 

controller.delegate = self; 

self.fetchedResultsController = controller; 

[controller release]; 

NSError *error = nil; 
[self.fetchedResultsController performFetch:&error]; 
if (error) { 
    // TODO send error notification 
    NSLog(@"%@", [error localizedDescription]); 
} 

El problema es que, inicialmente, la tienda tiene ninguna entidad mientras se descarga y se sincroniza de un servicio web. Lo que sucede es que NSFetchedResultsController llena la tabla con más de 150 filas de entidades de la tienda, que es la cantidad que devuelve el servicio web. Pero estoy estableciendo un límite de búsqueda de 20 que parece estar ignorando. Sin embargo, si cierro la aplicación y empiezo de nuevo con los datos que ya están en la tienda, funciona bien. Im mi delegado hago esto:

#pragma mark - 
#pragma mark NSFetchedResultsControllerDelegate methods 

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 
[self.tableView beginUpdates]; 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo 
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { 

switch(type) { 
    case NSFetchedResultsChangeInsert: 
     [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeDelete: 
     [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 
    } 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject 
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 

UITableView *tableView = self.tableView; 

switch(type) { 

    case NSFetchedResultsChangeInsert: 
     [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeDelete: 
     [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeUpdate: 
     [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; 
     break; 

    case NSFetchedResultsChangeMove: 
     [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 
    } 
} 


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { 
[self.tableView endUpdates]; 
} 

que es prácticamente copiar y pegar de los documentos dev de Apple, alguna idea de lo que está pasando en?

Respuesta

1

El problema que tiene es que está llamando antes de cargar fetchedResultsController cargar los datos completos por lo que te muestra todo lo que necesita hacer es cargar toda la información y luego llamar fetchedResultsController

Ejemplo

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    // Loading Articles to CoreData 
    [self loadArticle]; 
} 

- (void)ArticleDidLoadSuccessfully:(NSNotification *)notification { 
    NSError *error; 
    if (![[self fetchedResultsController] performFetch:&error]) { 
     // Update to handle the error appropriately. 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
     abort(); // Fail 
    } 
    [tableView reloadData]; 
} 
+0

Suena bien Voy a dar una oportunidad y le haré saber lo que sucede. – marchinram

+0

No estoy tan seguro de que esto sea necesario ... ¿funcionó? –

+0

Me pregunto por qué las personas aún ponen abort(); en el código. La aplicación será rechazada por eso ;-) el manejo adecuado de errores es suficiente (o simplemente use NSAssert) – Lukasz

5

Esta es una vieja pregunta, pero me encontré con ella (en iOS 5). Creo que te encuentras con el error descrito aquí: https://devforums.apple.com/message/279576#279576.

Ese subproceso proporciona soluciones en función de si tiene una sectionNameKeyPath o no. Como I (como usted) no lo hizo, la respuesta es desacoplar la vista de tabla de fetchedResultsController. Por ejemplo, en vez de usarlo para determinar el número de filas:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
     return [[[self.fetchedResultsController sections] objectAtIndex:0] numberOfObjects]; 

acaba de regresar lo que se espera:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
     return fetchLimit; 

Y en controller:didChangeObject, solamente insertar el nuevo objeto si el newIndexPath está dentro de su fetchlimit .

+5

Este error aún no está solucionado en iOS 6 o 7. – bugloaf

+4

Puedo confirmar que todavía existe en iOS 8. – Devfly

+4

Aparentemente el error existe en iOS 9 también – spassas

11

Sé que esto es una vieja pregunta, pero tengo una solución para ello:

Dado que hay un error conocido en NSFetchedResultsController que no honra al fetchlimit del NSFetchRequest, usted tiene que manejar manualmente la limitación de de registros dentro de sus métodos UITableViewDataSource y NSFetchedResultsControllerDelegate.

tableView: numberOfRowsInSection:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 

    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; 

    NSInteger numRows = [sectionInfo numberOfObjects]; 

    if (numRows > self.fetchedResultsController.fetchRequest.fetchLimit) { 

     numRows = self.fetchedResultsController.fetchRequest.fetchLimit; 
    } 

    return numRows; 
} 

controlador: didChangeObject: atIndexPath: forChangeType: newIndexPath:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 

    switch(type) { 

     case NSFetchedResultsChangeInsert: 

      if ([self.tableView numberOfRowsInSection:0] == self.fetchedResultsController.fetchRequest.fetchLimit) { 
       //Determining which row to delete depends on your sort descriptors 
       [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:self.fetchedResultsController.fetchRequest.fetchLimit - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade]; 

      } 

      [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] 
          withRowAnimation:UITableViewRowAnimationFade]; 
     break; 
     ... 
    } 
} 
+1

Recientemente me ha tocado esto en iOS 7, y creo que esta es la mejor solución, pero Realmente no entiendo cómo Apple no ha solucionado esto todavía. Es hora de un informe de error, supongo –

+3

Parece que no puedo solucionar este error con el código anterior. Aún así obtengo una excepción y el siguiente registro de consola: " CoreData: error: error de aplicación grave. Se detectó una excepción del delegado de NSFetchedResultsController durante una llamada a -controllerDidChangeContent: intento de inserte la fila 50 en la sección 0, pero solo hay 50 filas en la sección 0 después de la actualización con userInfo (null) " –

2

Estos seguirá siendo acelerado en algunas situaciones, como varias plantillas, o movimiento sobre el limite,... Usted tiene que guardar todos los cambios a 4 series, y calcular otras 4 matrices y eliminar/actualizar/insertar a tableView antes -[UITableView endUpdates]

algo como (se supone que sólo hay una sección):

NSUInteger limit = controller.fetchRequest.fetchLimit; 
NSUInteger current = <current section objects count>; 
NSMutableArray *inserts = [NSMutableArray array]; 
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"row < %d", limit]; 

if (insertedIndexPaths.count) { 
    NSUInteger deletedCount = 0; 
    for (NSIndexPath *indexPath in insertedIndexPaths) { 
     if (indexPath.row >= limit) continue; 
      current++; 
      if (current > limit) { 
       deletedCount++; 
       current--; 
       [deletedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - deletedCount inSection:indexPath.section]]; 
      } 
      [inserts addObject:indexPath]; 
    } 
} 
if (movedIndexPaths.count) { 
    for (NSIndexPath *indexPath in movedIndexPaths) { 
     if (indexPath.row >= limit) { 
      [updatedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - 1 inSection:indexPath.section]]; 
     } else { 
      [inserts addObject:indexPath]; 
     } 
} 
} 
[updatedIndexPaths minusSet:deletedIndexPaths]; 
[deletedIndexPaths filterUsingPredicate:predicate]; 
[updatedIndexPaths filterUsingPredicate:predicate]; 
[_tableView insertRowsAtIndexPaths:inserts withRowAnimation:UITableViewRowAnimationFade]; 
[_tableView reloadRowsAtIndexPaths:[updatedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationNone]; 
[_tableView deleteRowsAtIndexPaths:[deletedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationFade]; 

[_tableView endUpdates]; 
deletedIndexPaths = nil; 
insertedIndexPaths = nil; 
updatedIndexPaths = nil; 
1

presenté un informe de error con apple de nuevo en 2014 en iOS 6/7 sobre este tema. Como muchos otros han notado, todavía es un error en iOS 9 y 10. Mi informe de error original todavía está abierto sin comentarios de Apple. Here is an OpenRadar copy of that bug report.

He aquí una solución que he utilizado con éxito, pero se llamará varias veces. Usar con precaución.

@objc func controllerDidChangeContent(controller: NSFetchedResultsController) { 
    tableView.endUpdates() // Only needed if you're calling tableView.beginUpdates() in controllerWillChangeContent. 

    if controller.fetchRequest.fetchLimit > 0 && controller.fetchRequest.fetchLimit < controller.fetchedObjects?.count { 
      controller.performFetch() 
      // Reload the table view section here 
     } 
    } 
} 
0

Esta es mi truco:

puse el delegado del NSFetchedResultsController después del método de 'salvar' en la instancia NSManagedObjectContext se llama.

  1. Establezca un observador en su UIViewController con un nombre: ej. 'Sync'
  2. Después de guardar su contexto, enviar una notificación con ese nombre: 'Sync' y desencadenar una función (en su viewcontroller) que establece el delegado

ps. recuerde quitar ese observador si ya no lo necesita

Cuestiones relacionadas