11

Ứng dụng của tôi có hai thanh tab ... Mỗi ứng dụng sẽ đưa người dùng đến một trình điều khiển tableview mà trình bày cho anh ta một danh sách các mục. Giao diện đầu tiên cho phép người dùng ghi lại các mục trong cơ sở dữ liệu. Tab/lượt xem khác đọc từ cơ sở dữ liệu và hiển thị các mục đó cho người dùng, tuy nhiên, không có bản cập nhật nào được thực hiện cho kho lưu trữ coreData/persistant từ chế độ xem thứ hai này.Lỗi CoreData khiến tôi phát điên ... CoreData: Lỗi ứng dụng nghiêm trọng. Một ngoại lệ được lấy từ đại biểu NSFetchedResultsController

Khi tôi thêm một mục mới thông qua bộ điều khiển chế độ xem đầu tiên, nó sẽ hiển thị hoàn hảo trong chế độ xem. Tuy nhiên, ngay khi tôi chạm vào thanh tab khác để xem mục mới xuất hiện trong chế độ xem đó, tôi nhận được lỗi được liệt kê bên dưới và mục mới được thêm vào không xuất hiện ... Lưu ý: nếu tôi dừng ứng dụng và tải lại/chạy lại nó và bắt đầu bằng cách nhấn vào thanh tab thứ 2, mục mới sẽ hiển thị tốt, vì vậy tôi biết mô hình đang được cập nhật tốt.

*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046 
2011-10-20 20:56:15.117 Gtrac[72773:fb03] CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null) 

Mã từ các ứng dụng đại diện trong đó các managedObjectContext được chuyển cho hai viewControllers.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 


    // get a point to the master database context 
    NSManagedObjectContext *context = [self managedObjectContext]; 
    if (!context) { 
     // Handle the error. 
    } 

    // create tab bar controller and array to hold each of the tab-based view controllers that will appear as icons at bottom of screen 
    tabBarController = [[UITabBarController alloc] init]; 
    NSMutableArray *localControllersArray = [[NSMutableArray alloc] initWithCapacity:5];  


    // 
    // setup first tab bar item 
    // 
    // 
    // alloc the main view controller - the one that will be the first one shown in the navigation control 
    RootViewController *rootViewController = [[RootViewController alloc] initWithTabBar]; 

    // Pass the managed object context to the view controller. 
    rootViewController.managedObjectContext = context; 

    // create the navigation control and stuff the rootcontroller inside it 
    UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController]; 

    // set the master navigation control 
    self.navigationController = aNavigationController; 

    // add the navigaton controller as the first tab for the tab bar 
    [localControllersArray addObject:aNavigationController]; 

    [rootViewController release]; 
    [aNavigationController release];  


    // 
    // setup the other tab bar 
    // 
    // 

    // alloc the view controller 
    vcSimulator *vcSimulatorController = [[vcSimulator alloc] initWithTabBar]; 

    UINavigationController *blocalNavigationController = [[UINavigationController alloc] initWithRootViewController:vcSimulatorController]; 

    // Pass the managed object context to the view controller. 
    vcSimulatorController.managedObjectContext = context; 

    // add this controller to the array of controllers we are building 
    [localControllersArray addObject:blocalNavigationController]; 

    // release these guys, they are safely stored in the array - kill these extra references 
    [blocalNavigationController release]; 
    [vcSimulatorController release]; 


    // 
    // 
    // ok, all the tab bars are in the array - get crackin 
    // 
    // 
    // load up our tab bar controller with the view controllers 
    tabBarController.viewControllers = localControllersArray; 

    // release the array because the tab bar controller now has it 
    [localControllersArray release]; 

    [window addSubview:[tabBarController view]]; 
    [window makeKeyAndVisible]; 

    return YES; 




When I add a new item via the first viewcontroller, it shows up perfectly in the view. However, as soon as I tap on the other tab bar to see the new item appear in that viewcontroller, I get the error listed above, and the newly added item does not appear... Note: if I stop the app and reload/re-run it, and start by tapping the 2nd tabbar, the new item shows up fine, so I know the model is being updated fine. 

Here are the tableview delegate methods from the 2nd view controller. 



- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 

    [self.tableView beginUpdates]; 
} 


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

    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);  
    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)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { 
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);  

    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)controllerDidChangeContent:(NSFetchedResultsController *)controller { 
    [self.tableView endUpdates]; 

} 

Bất kỳ trợ giúp nào bạn có thể cung cấp sẽ được đánh giá cao.

Tôi đã tìm kiếm trang web này và tìm thấy nhiều trường hợp lỗi này nhưng không có trường hợp nào có vẻ khá phù hợp. Tôi cũng thấy tài liệu tham khảo suy luận rằng lỗi này tôi nhìn thấy thực sự là một lỗi được biết trong mã của Apple ...

* THÔNG TIN CẬP NHẬT *

Tôi đã đi lại và thiết lập điểm break trong mã và đang chỉnh sửa câu hỏi gốc với thông tin bổ sung này. Khi người dùng thêm một mục mới vào cơ sở dữ liệu, anh ta được chuyển từ chế độ xem gốc sang chế độ xem listCourses. Giao dịch bổ sung hoạt động hoàn hảo và UITableView của listCourses View được cập nhật hoàn hảo.

Khi tôi nhấp vào chế độ xem khác cũng đọc dữ liệu từ cùng một mô hình dữ liệu cốt lõi, đó là bộ điều khiển xem chạy qua chuỗi sau nhưng không bao giờ kết thúc thêm mục mới vào chế độ xem bảng. Đây là chuỗi nó đi qua.

Simulator VC:

- controllerWillChangeContent which runs... 
     [self.tableView beginUpdates]; 

    - didChangeObject 
     ..with message: NSFetchedResultsChangeUpdate 
     ..which ran: 
     [self configureCell:[tableView cellForRowAtIndexPath:indexPath] 

    - controllerDidChangeContent: 
     [self.tableView endUpdates]; 

Các viewController khác mà các công trình lớn, đi qua dãy này ngay lập tức sau khi kỷ lục được bổ sung vào cơ sở dữ liệu.

ListCourses VC:

- didChangeSection 
    ...with message: NSFetchedResultsChangeInsert 
...which ran: 
    [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 

- didChangeObject 
    ..with message: NSFetchedResultsChangeInsert 
    ..which ran: 
    [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 

Tại sao một viewController nhận được thông báo NSFetchedResultsChangeInsert nhưng một trong những khác không?

Dưới đây là các phương thức đại biểu từ bộ điều khiển xem bị lỗi.

// Override to support editing the table view. 
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    if (editingStyle == UITableViewCellEditingStyleDelete) { 
     // Delete the row from the data source 
     [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
    } 
    else if (editingStyle == UITableViewCellEditingStyleInsert) { 
     // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 
    } 
} 





- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates. 
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);  

    [self.tableView beginUpdates]; 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);  


    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: 

      //[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      [self configureCell:[tableView cellForRowAtIndexPath:indexPath] 
        atIndexPath:indexPath]; 
      //[tableView reloadData]; 


      break; 

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

      // Reloading the section inserts a new row and ensures that titles are updated appropriately. 
      // [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade]; 

      break; 
    } 
    NSLog(@"vc>>> about to reload data"); 
    // [self.tableView reloadData]; 

} 


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { 
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);  

    switch(type) { 

     case NSFetchedResultsChangeInsert: 
      [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 

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

} 


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { 
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates. 
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);  
    [self.tableView endUpdates]; 

} 

Cảm ơn, phil

Trả lời

20

Các UITableView sanity kiểm tra các công trình như thế này:

Tại dòng [self.tableView beginUpdates]; tableView gọi bảng của bạnView: numberOfRowsInSection: phương thức ủy nhiệm, có vẻ như đang trả về 3. Tại dòng [self.tableView endUpdates]; nó gọi nó một lần nữa và nó dường như trở lại 4.Do đó, tableView đang mong bạn chèn 1 hàng giữa hai dòng này. Trong thực tế, không có hàng nào được chèn vào để tableView không xác nhận. (Bạn có thể thấy số hàng dự kiến ​​và thực tế trong thông báo xác nhận).

Tăng từ 3 hàng lên 4 hàng cho thấy NSFetchedResultsController của bạn đang nhận thấy mục dữ liệu lõi mới được chèn chính xác. Những gì bạn cần làm là đặt một điểm dừng ở đầu của bộ điều khiển của bạn: didChangeObject: atIndexPath: forChangeType: phương pháp và bước qua nó khi bạn chuyển sang tab thứ 2 sau khi chèn một mục. Bạn sẽ thấy NSFetchedResultsChangeInsert: trường hợp của câu lệnh switch được thực hiện nhưng điều này rõ ràng là không xảy ra.

Hy vọng rằng, bạn có thể tìm ra lý do tại sao việc chèn không diễn ra - nếu không hãy quay lại và cho chúng tôi biết những gì bạn thực sự thấy khi bước qua phương pháp này.

Edited thêm:

OK, vì vậy NSFetchedResultsController phương pháp đại biểu của mình trong bộ điều khiển xem 2 được gọi là khi bạn chuyển sang tab đó chứ không phải là ngay lập tức khi mục mới được chèn vào tab 1. Điều này có nghĩa rằng 2 bộ điều khiển xem không nhìn thấy chèn (sẽ xảy ra ngay lập tức) và thực sự phản hồi một số thông báo cập nhật dữ liệu cốt lõi khác sau đó xảy ra khi bạn chuyển sang tab 2. Trình điều khiển kết quả tìm nạp đang làm việc với thông tin cũ tại dòng beginUpdates (có thực sự 4 mục trong tập kết quả ở đây không phải 3). Vào thời điểm nó được đến dòng endUpates nó đã làm mới lấy của nó và tìm thấy một chèn bất ngờ.

Phương pháp ủy nhiệm NSFetchedResultsController thực sự được thiết kế để cập nhật giao diện người dùng tại chỗ trong khi bạn đang thực hiện thay đổi VÀ chế độ xem của bộ điều khiển hiển thị. Trong trường hợp của bạn, bạn đang thực hiện thay đổi và THEN hiển thị bộ điều khiển chế độ xem mới. Các mô hình mà bạn thực sự nên được sử dụng là để làm mới tableview trong phương pháp viewWillAppear lại điều khiển 2. Một cái gì đó như thế này nên làm điều đó:

- (void)viewWillAppear:(BOOL)animated 
{ 
    [super viewWillAppear:animated]; 

    NSError *error = nil; 
    [resultsController performFetch:&error]; // Refetch data 
    if (error != nil) { 
     // handle error 
    } 

    [self.tableView reloadData]; 
} 

Điều này sẽ đảm bảo rằng bất cứ khi nào bạn chuyển sang tab 2 nó đang làm việc với dữ liệu mới từ mô hình.

+1

Cảm ơn bạn, Robin! Tôi sẽ làm theo và cho bạn biết vào buổi sáng ... 1 giờ sáng ở đây! – phil

+0

Tất cả, vui lòng tham khảo thông tin cập nhật ở cuối câu hỏi gốc. Tôi đã cung cấp các phương thức ủy nhiệm tableview và luồng cuộc gọi mà tôi thấy bằng cách phá vỡ các điểm và trình gỡ rối. Bất kỳ ý tưởng tại sao vcSimulator tableview phương pháp đại biểu của tôi không bao giờ nhận được thông báo NSFetchedResultsChangeInsert? – phil

+0

Đã chỉnh sửa câu trả lời ở trên. –

Các vấn đề liên quan