删除部分的最后一行后,应用程序崩溃

Posted

技术标签:

【中文标题】删除部分的最后一行后,应用程序崩溃【英文标题】:After deleting last row of a section, the app crashes 【发布时间】:2013-11-29 21:59:35 【问题描述】:

在我的 ios 应用程序中,我有一个包含部分的表格视图,所有部分都由核心数据填充。 如果删除部分的最后一行,则应用程序崩溃。 我无法找出我的代码中的问题所在,请您帮助我。先感谢您。到目前为止,这是我的代码:

#import "PersonsTVC.h"
#import "Person.h"

@implementation PersonsTVC
@synthesize fetchedResultsController = __fetchedResultsController;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize selectedPerson;
@synthesize searchResults,titulosseccion;



- (void)setupFetchedResultsController

    // 1 - Decide what Entity you want
    NSString *entityName = @"Person"; // Put your entity name here
    NSLog(@"Setting up a Fetched Results Controller for the Entity named %@", entityName);

    // 2 - Request that Entity
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];

    // 3 - Filter it if you want
    //request.predicate = [NSPredicate predicateWithFormat:@"Person.name = Blah"];

    // 4 - Sort it if you want
    // First sort descriptor (required for grouping into sections):
    NSSortDescriptor *sortByDate = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
    // Second sort descriptor (for the items within each section):
    NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:@"firstname" ascending:YES];
    [request setSortDescriptors:@[sortByDate, sortByName]];


   //
   // request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"firstname"
                                                                                     //ascending:YES
                                                                                      //selector:@selector(localizedCaseInsensitiveCompare:)]];
    // 5 - Fetch it
    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                        managedObjectContext:self.managedObjectContext
                                                                          sectionNameKeyPath:@"sectionIdentifier"
                                                                                   cacheName:nil];
    [self performFetch];





- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section

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

    NSString *sectionName = [theSection name];
    if ([sectionName isEqualToString:@"0"]) 
        return @"Today";
     else if ([sectionName isEqualToString:@"1"]) 
        return @"Tomorrow";
    
    else if ([sectionName isEqualToString:@"2"]) 
        return @"Upcoming";
    
    return @"Other";


- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView

    // Return the number of sections.

    return [[self.fetchedResultsController sections] count];




- (void) viewDidLoad




    self.searchResults = [NSMutableArray arrayWithCapacity:[[self.fetchedResultsController fetchedObjects] count]];
    [self.tableView reloadData];









- (void)viewWillAppear:(BOOL)animated

    [super viewWillAppear:animated];
    [self setupFetchedResultsController];





- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath


    // Perform segue to detail when a SEARCH table cell is touched
    if(tableView == self.searchDisplayController.searchResultsTableView)
    
        [self performSegueWithIdentifier:@"Person Detail Segue" sender:tableView];
    





- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    static NSString *CellIdentifier = @"Persons Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) 
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    

    // Configure the cell...
    // Configure the cell...
    Person  *person = nil;

    if (tableView == self.searchDisplayController.searchResultsTableView)
    
        NSLog(@"Configuring cell to show search results");
        person = [self.searchResults objectAtIndex:indexPath.row];
    
    else
    
        NSLog(@"Configuring cell to show normal data");
        person = [self.fetchedResultsController objectAtIndexPath:indexPath];
    








    NSString *fullname = [NSString stringWithFormat:@"%@ %@", person.firstname, person.surname];
    cell.textLabel.text = person.firstname;
    if ([person.inRole.color isEqual :@"Yellow"])
    
        cell.imageView.image = [UIImage imageNamed:@"Yellow"];
    
    if ([person.inRole.color isEqual :@"Black"])
    
        cell.imageView.image = [UIImage imageNamed:@"Black"];
    
    if ([person.inRole.color isEqual :@"Grey"])
    
        cell.imageView.image = [UIImage imageNamed:@"Grey"];
    
    if ([person.inRole.color isEqual :@"Red"])
    
        cell.imageView.image = [UIImage imageNamed:@"Red"];
    
    if ([person.inRole.color isEqual :@"Blue"])
    
        cell.imageView.image = [UIImage imageNamed:@"Blue"];
    
    if ([person.inRole.color isEqual :@"Dark Green"])
    
        cell.imageView.image = [UIImage imageNamed:@"DarkGreen"];
    
    if ([person.inRole.color isEqual :@"Light Green"])
    
        cell.imageView.image = [UIImage imageNamed:@"LightGreen"];
    
    if ([person.inRole.color isEqual :@"Light Blue"])
    
        cell.imageView.image = [UIImage imageNamed:@"LightBlue"];
    
    if ([person.inRole.color isEqual :@"Brown"])
    
        cell.imageView.image = [UIImage imageNamed:@"Brown"];
    
    if ([person.inRole.color isEqual :@"Dark Orange"])
    
        cell.imageView.image = [UIImage imageNamed:@"DarkOrange"];



    

    NSDate *fechasinformat = person.date;
    NSString *fecha0 = [NSString stringWithFormat:@"%@", fechasinformat];


   cell.detailTextLabel.text = fecha0;



    return cell;



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

    if (tableView == self.searchDisplayController.searchResultsTableView)
    
        return [self.searchResults count];
    
    else
    
        return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
    




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




    if (editingStyle == UITableViewCellEditingStyleDelete) 

        [self.tableView beginUpdates]; // Avoid  NSInternalInconsistencyException

        // Delete the person object that was swiped
        Person *personToDelete = [self.fetchedResultsController objectAtIndexPath:indexPath];
        NSLog(@"Deleting (%@)", personToDelete.firstname);
        [self.managedObjectContext deleteObject:personToDelete];
        [self.managedObjectContext save:nil];

        // Delete the (now empty) row on the table
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];




        [self performFetch];





        [self.tableView endUpdates];
    


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

    if ([segue.identifier isEqualToString:@"Add Person Segue"])
    
        NSLog(@"Setting PersonsTVC as a delegate of PersonDetailTVC");
        PersonDetailTVC *personDetailTVC = segue.destinationViewController;
        personDetailTVC.delegate = self;

        NSLog(@"Creating a new person and passing it to PersonDetailTVC");
        Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
                                                          inManagedObjectContext:self.managedObjectContext];

        personDetailTVC.person = newPerson;
    
    else if ([segue.identifier isEqualToString:@"Person Detail Segue"])
    
        NSLog(@"Setting PersonsTVC as a delegate of PersonDetailTVC");
        PersonDetailTVC *personDetailTVC = segue.destinationViewController;
        personDetailTVC.delegate = self;

        // Store selected Person in selectedPerson property
        if(sender == self.searchDisplayController.searchResultsTableView)
        
            NSIndexPath *indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
            self.selectedPerson = [self.searchResults objectAtIndex:[indexPath row]];
        
        else
        
            NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
            self.selectedPerson = [self.fetchedResultsController objectAtIndexPath:indexPath];
        

        NSLog(@"Passing selected person (%@) to PersonDetailTVC",    self.selectedPerson.firstname);
        personDetailTVC.person = self.selectedPerson;
    
    else
    
        NSLog(@"Unidentified Segue Attempted!");
    


- (void)theSaveButtonOnThePersonDetailTVCWasTapped:(PersonDetailTVC *)controller

    // do something here like refreshing the table or whatever

    // close the delegated view
    [controller.navigationController popViewControllerAnimated:YES];    


#pragma mark -
#pragma mark Content Filtering

-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope 
    self.searchResults = [[self.fetchedResultsController fetchedObjects] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) 
        Person* person = evaluatedObject;
        NSString* firstName = person.firstname;

        //searchText having length < 3 should not be considered
        if (!!searchText && [searchText length] < 3) 
            return YES;
        

        if ([scope isEqualToString:@"All"] || [firstName isEqualToString:scope])  
            return ([firstName rangeOfString:searchText].location != NSNotFound);
        
        return NO; //if nothing matches
    ]];


#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString

    [self filterContentForSearchText:searchString scope:@"All"];
    return YES;


- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption

    [self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:@"All"];
    return YES;



@end

【问题讨论】:

这是错误日志:由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无效更新:无效的节数。更新后表视图中包含的节数(1)必须等于更新前表视图中包含的节数(1),加上或减去插入或删除的节数(0插入,1已删除)。' 你实现了 fetchedResultsController 委托方法吗?如果不这样做,让他们处理任何 UITableView 更新。 嗨,邓肯,我不知道该怎么做....谢谢 阅读此developer.apple.com/library/ios/documentation/CoreData/… 感谢 Duncan,我已阅读文档,但我没有经验知道如何实施所有这些信息,无论如何,谢谢... 【参考方案1】:

错误清楚地表明您删除了 1 个部分,删除前有 1 个,删除后有 1 个。这就是问题。所以可能你的

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView

删除最后一个对象后返回不正确的节数。

在删除最后一个对象后,您的 [[self.fetchedResultsController sections] count] 不为 0,或者 numberOfSectionsInTableView:(UITableView *)tableViewself.searchDisplayController.searchResultsTableView 调用,而您没有进行检查。

编辑:要解决此问题,请添加以下代码:

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView

    // Return the number of sections.
    if(tableView==self.searchDisplayController.searchResultsTableView)
        NSLog("search table asking");
    else
        NSLog("main table asking");
    NSLog("fetched sections: %d", [[self.fetchedResultsController sections] count]);
    return [[self.fetchedResultsController sections] count];

并在崩溃发生时发布控制台输出。

【讨论】:

谢谢@GWiz,我能做些什么来解决这个问题?你会帮我吗? 我更正了我的编辑,以确保我们知道哪个表正在“询问”其节数 谢谢@gWiz,我现在必须去一段时间,回来我会告诉你它是如何工作的。再次感谢您。 应用崩溃后显示第二个NSLog,添加一行后显示:fetchedsections:1,删除后:fetchedsections:0 崩溃前是否显示“搜索表询问”或“主表询问”?【参考方案2】:

要实现 NSFetchedResultsController 委托方法,请将以下代码添加到您的 viewController(我删除了一些代码以便检查它是否编译),并在创建控制器时使用 _fetchedResultsController.delegate = self; 之类的东西将 viewController 设置为委托(在 setupFetchedResultsController 中) .

只要添加、修改或删除 fetchedResultsController 结果集中的 managedObjects,就会调用这些方法。您可以看到它们包含对 UITableView 的必要调用以更新显示。这些与使用 fetchedResultsController 作为 UITableView 的数据源一起使用 - 这样您就不会遇到任何不一致的错误。

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller

    NSLog(@"controllerWillChangeContent: called");
    [self.tableView beginUpdates];


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
   NSLog(@"didChangeSection: called");
    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

    NSLog(@"controller:didChangeObject: called");
    NSLog(@" object is %@", [anObject valueForKey:@"displayName"]);
        UITableView *tableView = self.tableView;

        switch(type) 
            case NSFetchedResultsChangeInsert:
                NSLog(@"NSFetchedResultsChangeInsert: called");
                [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

            
                break;

            case NSFetchedResultsChangeDelete:
               NSLog(@"NSFetchedResultsChangeDelete: called");
                [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];

            
                break;

            case NSFetchedResultsChangeUpdate:
               NSLog(@"NSFetchedResultsChangeUpdate: called");
                [self configureCell:tableView cell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            
                break;

            case NSFetchedResultsChangeMove:
               NSLog(@"NSFetchedResultsChangeMove: called");
                self.changedObjectIndex = newIndexPath;

                [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            
                break;
        



// NOTE: This is overridden by subclasses to modify the default behaviour
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

    NSLog(@“controllerDidChangeContent: called");
        [self.tableView endUpdates];


- (void)configureCell:(UITableView *)tableView cell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath

    NSManagedObject *object;

    object = [self.fetchedResultsController objectAtIndexPath:indexPath];

    cell.textLabel.text = [[object valueForKey:@"displayName"] description];
    bool found =[[object valueForKey:@"found"] boolValue];

    if (found) 
        cell.textLabel.textColor = [UIColor greenColor];
    


【讨论】:

【参考方案3】:

适当的解决方案是让您的PersonsTVC 成为获取结果控制器的代表。 如果您打开一个使用 CoreData 的空项目,您将有更好的理解,并且可能会找到您尝试完成的解决方案。

您的代码应该是最简单的形式:

- (void)setupFetchedResultsController

    //Setup your fetch request ...

    NSFetchedResultsController* controller =
    [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                        managedObjectContext:self.managedObjectContext
                                          sectionNameKeyPath:@"sectionIdentifier"
                                                   cacheName:nil];
    //setup the delegation and retain the FRC
    controller.delegate = self;
    self.fetchedResultsController = controller;

    NSError* error = nil;
    if (![controller performFetch:&error]) 
        NSLog(@"error performing fetch: %@",error);
        abort();
    
    //Not sure what this does, seems redundant
    //[self performFetch];

现在您必须通过您创建的 FRC 访问您的托管对象(不知道为什么需要self.searchResults)。

正如我所说,以最简单的形式实现:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

    // In the simplest, most efficient, case, reload the table view.
    [self.tableView reloadData];

注意:您的删除代码应为: 这也是您的应用程序崩溃的原因,因为您在 FRC 能够报告上下文更改之前更新了表格。

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

    if (editingStyle == UITableViewCellEditingStyleDelete) 
        NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
        [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

        NSError *error = nil;
        if (![context save:&error]) 
            NSLog(@"Unresolved error %@", error);
            abort();
        
       

【讨论】:

谢谢@Dan Shelly,您的提议使我的应用程序崩溃但出现其他错误。我正在使用部分和搜索栏... 什么是新的崩溃?您应该删除对表的任何“手动”更新,并使用 FRC 委托方法使用的更新。

以上是关于删除部分的最后一行后,应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章

使用 NSFetchedResultsController 删除部分中的最后一行 -> 崩溃

删除部分中的最后一个单元格后,iOS 应用程序崩溃

moveRowAtIndexPath:如何在最后一行移动到另一个部分后删除部分

tableView reloadData 导致应用在删除部分后崩溃

从 UITableView 中删除最后一行 - 因 NSInternalInconsistencyError 而崩溃

从部分移动最后一行时如何删除部分标题