Нужна помощь с fetchedResultsController

#iphone #objective-c #core-data

#iPhone #objective-c #core-данные

Вопрос:

У меня был tableview, который был заполнен из массива, который был заполнен из полученных результатов.

Теперь я пытаюсь просто использовать fetchedResultsController так, как рекомендует Apple, и не использовать отдельный массив для загрузки данных.

В этом процессе преобразования я теперь застрял. Мне нужно удалить все ссылки на eventsArray и использовать свойство FechedObjects fetchedResultsController. Если бы кто-нибудь мог мне с этим помочь, это было бы здорово.

Вот мой код:

 @implementation RoutineTableViewController

@synthesize routineTableView;
@synthesize eventsArray;
@synthesize entered;
@synthesize managedObjectContext;
@synthesize fetchedResultsController=__fetchedResultsController;

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    if (managedObjectContext == nil) 
    { 
        managedObjectContext = [(CurlAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; 
    }

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Routine" inManagedObjectContext:managedObjectContext];
    [request setEntity:entity];

    NSError *error = nil;
    NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:amp;error] mutableCopy];
    if (mutableFetchResults == nil) {
        // Handle the error.
    }
    [self setEventsArray:mutableFetchResults];
    [mutableFetchResults release];
    [request release];

    UIBarButtonItem * addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(showPrompt)];
    [self.navigationItem setLeftBarButtonItem:addButton];
    [addButton release];

    UIBarButtonItem *editButton = [[UIBarButtonItem alloc]initWithTitle:@"Edit" style:UIBarButtonItemStyleBordered target:self action:@selector(toggleEdit)];
    self.navigationItem.rightBarButtonItem = editButton;
    [editButton release];

    [super viewDidLoad];
}

- (void)viewDidUnload
{
    self.eventsArray = nil;
    [super viewDidUnload];
}

-(void)toggleEdit
{
    [self.routineTableView setEditing: !self.routineTableView.editing animated:YES];

    if (self.routineTableView.editing)
        [self.navigationItem.rightBarButtonItem setTitle:@"Done"];
    else
        [self.navigationItem.rightBarButtonItem setTitle:@"Edit"];
}

- (void)dealloc
{
    [__fetchedResultsController release];
    [managedObjectContext release];
    [eventsArray release];
    [entered release];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

#pragma mark -
#pragma mark Add an event

-(void)addEvent
{    
    Routine *routine = (Routine *)[NSEntityDescription insertNewObjectForEntityForName:@"Routine" inManagedObjectContext:managedObjectContext];

    routine.name=entered;

    NSError *error = nil;
    if (![managedObjectContext save:amp;error]) 
    {
        // Handle the error.
    }
    NSLog(@"%@", error);

    [eventsArray addObject:routine];

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];

    //[self.routineTableView reloadData];

    NSInteger lastSection = [self.routineTableView numberOfSections] -1;


    [self.routineTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.routineTableView numberOfRowsInSection:lastSection]-1 inSection:lastSection] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
/*
- (void)insertNewObject
{
    // Create a new instance of the entity managed by the fetched results controller.
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];

    // If appropriate, configure the new managed object.
    // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
    [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];

    // Save the context.
    NSError *error = nil;
    if (![context save:amp;error])
    {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
         */
/*
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}
*/

-(void)showPrompt
{
    AlertPrompt *prompt = [AlertPrompt alloc];
    prompt = [prompt initWithTitle:@"Add Workout Day" message:@"n n Please enter title for workout day" delegate:self cancelButtonTitle:@"Cancel" okButtonTitle:@"Add"];
    [prompt show];
    [prompt release];
}

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{

    if (buttonIndex != [alertView cancelButtonIndex])
    {
        entered = [(AlertPrompt *)alertView enteredText];

        if(eventsArray amp;amp; entered)
        {
            [self addEvent];

        }
    }
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }

    Routine *tempRoutine = (Routine *)[eventsArray objectAtIndex:indexPath.row];
    cell.textLabel.text = tempRoutine.name;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

    return cell;
}

// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the specified item to be editable.
    return YES;
}

-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
 {

     if (editingStyle == UITableViewCellEditingStyleDelete) {

         // Delete the managed object for the given index path
         NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
         [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

         // Update the array and table view.
         [eventsArray removeObjectAtIndex:indexPath.row];
         [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];

         // Commit the change.
         NSError *error = nil;
         if (![managedObjectContext save:amp;error]) {
             // Handle the error.
         }
     }
 }

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[managedObject valueForKey:@"name"] description];
}

/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/

/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the item to be re-orderable.
    return YES;
}
*/

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here. Create and push another view controller.
    /*
     <#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@"<#Nib name#>" bundle:nil];
     // ...
     // Pass the selected object to the new view controller.
     [self.navigationController pushViewController:detailViewController animated:YES];
     [detailViewController release];
     */
}

#pragma mark - Fetched results controller

- (NSFetchedResultsController *)fetchedResultsController
{
    if (__fetchedResultsController != nil)
    {
        return __fetchedResultsController;
    }

    /*
     Set up the fetched results controller.
     */
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Routine" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    [aFetchedResultsController release];
    [fetchRequest release];
    [sortDescriptor release];
    [sortDescriptors release];

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:amp;error])
    {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return __fetchedResultsController;
}    

#pragma mark - Fetched results controller delegate

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

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.routineTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.routineTableView 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.routineTableView;

    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.routineTableView endUpdates];
}

/*
 // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed. 

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
 {
 // In the simplest, most efficient, case, reload the table view.
 [self.tableView reloadData];
 }
 */

@end
  

Ошибка:

  2011-04-14 00:35:14.668 Curl[9389:207] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableView.m:995
2011-04-14 00:35:14.671 Curl[9389:207] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (7) must be equal to the number of rows contained in that section before the update (7), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).'
  

Комментарии:

1. почему вы не используете fetchedResultsController напрямую, а не eventsArray?

2. Это то, что я пытаюсь сделать, Равин

3. Мой вопрос в том, почему у вас все еще есть eventsArray? в cellForRowAtIndexPath вы устанавливаете текст с помощью eventsArray, но в других методах источника данных вы используете fetchedResultsController. почему вы не используете его с самого начала?

4. Ранее я использовал eventsArray для всего. Я хочу избавиться от всего, что связано с eventsArray, и использовать fetchedResultsController. Я не знаю, как задать текст с помощью fetchedResultsController, поэтому у меня все еще есть eventsArray там.

Ответ №1:

попробуйте это,

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }


    Routine *tempRoutine = (Routine *)[yourFetchController objectAtIndexPath:indexPath];
    cell.textLabel.text = tempRoutine.name;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

    return cell;
}
  

или

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }


    NSManagedObject *managedObject = [<#Fetched results controller#> objectAtIndexPath:indexPath];
    cell.textLabel.text = managedObject.name;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

    return cell;
}
  

Обновить

 if (editingStyle == UITableViewCellEditingStyleDelete) {

        // Delete the managed object for the given index path
        NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
        [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

        // Update the array and table view.
        //[eventsArray removeObjectAtIndex:indexPath.row];
if (![managedObjectContext save:amp;error]) {
            // Handle the error.
        }
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];

        // Commit the change.
        NSError *error = nil;

    }
  

чтобы отключить сортировку, прокомментируйте следующую строку в - (NSFetchedResultsController *)fetchedResultsController методе.

  // Edit the sort key as appropriate.
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

        [fetchRequest setSortDescriptors:sortDescriptors];
  

Комментарии:

1. Это устраняет проблему, спасибо. Мне просто нужно избавиться от остальных ссылок на eventsArray сейчас.

2. Что мне делать с addEvent методом [eventsArray addObject:routine]; ?

3. прокомментируйте это и попробуйте. главное — обновить данные и перезагрузить таблицу.

4. У меня все еще возникают проблемы с остальными местами, где был eventsArray. например, в строке удаления и viewDidLoad ‘s [self setEventsArray:mutableFetchResults];

5. На самом деле я прокомментировал эту строку viewDidLoad, и, похоже, это не имеет значения. Однако, когда я удаляю ячейки из таблицы, мне нужно исправить следующую строку: [eventsArray removeObjectAtIndex:indexPath.row];