【问题标题】:NSFetchedResultsController: Multiple FRCs, Delegate Error when UpdatingNSFetchedResultsController:多个 FRC,更新时委托错误
【发布时间】:2014-08-24 17:17:15
【问题描述】:

目标: 使用 FRC,将 Section'sstartDate 排序,NSDate 属性,但希望 Today 的日期 Section 出现在 Upcoming 日期 @987654332 之前@。

我使用瞬态属性 sectionIdentifier 遵循 Apple 的代码。 Apple's sample code。并首先从这个项目开始:OneFRC

我很快意识到仅使用一个 FRC 可能无法做到这一点(我可能错了)。

接下来,我决定尝试使用 3 个 FRCThreeFRC

TableView sections 现在出现在我想要的订单中:

第 0 节:Today

第 1 部分:Upcoming

第 2 部分:Past

但是,添加数据会触发 FRC 委托,我收到以下错误:

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)

再说一次,我希望能够用 1 FRC 实现我的目标,但我似乎不知道如何。

我已经尝试解决这个问题 4 天了!如果这个问题在 SO 上没有得到解决,我想我可能会联系 Apple 以获得开发人员支持。如果我这样做了,我会在此处发布解决方案,以便其他人受益。

项目在 Github 上可用:

One FRC

Three FRC

编辑

感谢@blazejmar,我能够摆脱rows 错误。但是,现在当我尝试添加 sections 时出现错误。

2014-11-03 16:39:46.852 FRC[64305:60b] CoreData: error: Serious application error.  
An exception was caught from the delegate of NSFetchedResultsController during a 
call to -controllerDidChangeContent:.  Invalid update: invalid number of sections.  
The number of sections contained in the table view after the update (2) must be 
equal to the number of sections contained in the table view before the update (1), 
plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). 
with userInfo (null)

在三个 FRC 中重现错误的步骤:

1. Launch App -> 
2. Tap Generate Data -> 
3. Tap View in FRC -> 
4. Tap back to the RootVC ->
5. Change the system date to a month from Today -> 
6. Tap View in FRC and only one section `Past` should appear. -> 
7. Tap `Add Data`.  
8. The error should appear in the log. 

【问题讨论】:

    标签: ios uitableview core-data ios7 nsfetchedresultscontroller


    【解决方案1】:

    我查看了您的 ThreeFRC 项目并注意到在- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 中您检查了哪些 FRC 包含对象,这将确定部分的数量。这是合乎逻辑的,但在添加/删除“部分”时(或者,当您的其他 FRC 突然有对象时)确实使 FRC 委托感到困惑。例如,您只有一个过去部分(1 个部分),但随后数据发生变化,因此您现在也有一个今天部分。由于 sectionPastFRC 或其他 FRC 没有任何部分更改,因此没有调用 - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type,虽然您现在应该有 2 个部分,但没有调用添加、删除或移动部分。您必须以某种方式手动更新这些部分,这可能会很痛苦。

    这是我建议的解决方法:因为每个 FRC 总是最多有一个部分,所以您应该在 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 中返回 3。这样在添加/删除一个部分时将不再有任何问题,因为它们都已经存在了。无论如何,例如,如果 Today 部分没有对象,则只需在 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 中返回 0。只需确保在- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 中,如果 fetchedObjects==0,则返回 nil,这样如果该部分没有对象,它也不会显示该部分标题。在您的 FRC 委托 didChangeObject 中,始终在对 tableView 执行更改之前调整 indexPath 和 newIndexPath。

    请注意,只有当您已经知道 FRC(最后一个 FRC 除外)需要的最大部分数时,此解决方法才有效。它不是单个表视图中多个 FRC 的所有实现的解决方案。我实际上在一个项目中使用了这个解决方案,其中一个 tableView 有 2 个 FRC,但第一个 FRC 总是只占用 1 个部分,而第二个 FRC 可以有任意数量的部分。我总是只需要根据第二个 FRC 的变化调整 +1 部分。

    我实际上已经尝试将上面提到的更改应用到您的代码中,并且没有出现错误。这是我在 UITableViewDataSource 中更改的部分:

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 3;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        NSInteger rows = 0;
        switch (section) {
            case 0:
            {
                rows = [[self.sectionTodayFRC fetchedObjects]count];
                break;
            }
            case 1:
            {
                rows = [[self.sectionUpcomingFRC fetchedObjects]count];
                break;
            }
            case 2:
            {
                rows = [[self.sectionPastFRC fetchedObjects]count];
                break;
            }
        }
        NSLog(@"Section Number: %i Number Of Rows: %i", section,rows);
        return rows;
    }
    
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    {
        NSString *header;
        switch (section) {
            case 0:
            {
                if ([[self.sectionTodayFRC fetchedObjects]count] >0)
                {
                    header = @"Today";
                }
                break;
            }
            case 1:
            {
                if ([[self.sectionUpcomingFRC fetchedObjects]count] >0)
                {
                    header = @"Upcoming";
                }
                break;
            }
            case 2:
            {
                if ([[self.sectionPastFRC fetchedObjects]count] >0)
                {
                    header = @"Past";
                }
                break;
            }
    
        }
    
        return  header;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    
        if (cell == nil)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                           reuseIdentifier:CellIdentifier];
        }
        Meeting *meeting;
        switch (indexPath.section) {
            case 0: 
                if ([[self.sectionTodayFRC fetchedObjects]count] > 0)
                {
                    meeting = [[self.sectionTodayFRC fetchedObjects] objectAtIndex:indexPath.row];
                }
                break;
    
            case 1:
                if ([[self.sectionUpcomingFRC fetchedObjects]count] > 0)
                {
                    meeting = [[self.sectionUpcomingFRC fetchedObjects] objectAtIndex:indexPath.row];
                }
                break;
    
            case 2:
                if ([[self.sectionPastFRC fetchedObjects]count] > 0)
                {
                    meeting = [[self.sectionPastFRC fetchedObjects] objectAtIndex:indexPath.row];
                }
                break;
        }
    
        cell.textLabel.text = meeting.title;
        return cell;
    }
    

    对于 NSFetchedResultsControllerDelegate:

    - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
        NSLog(@"Inside didChangeObject:");
    
        NSIndexPath *modifiedIndexPath;
        NSIndexPath *modifiedNewIndexPath;
    
        if (controller == self.sectionTodayFRC)
        {
            modifiedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:0];
            modifiedNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:0];
        }
        else if (controller == self.sectionUpcomingFRC)
        {
            modifiedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:1];
            modifiedNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:1];
        }
        else if (controller == self.sectionPastFRC)
        {
            modifiedIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:2];
            modifiedNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:2];
        }
    
        switch(type) {
    
            case NSFetchedResultsChangeInsert:
                [self.tableView insertRowsAtIndexPaths:@[modifiedNewIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
    
            case NSFetchedResultsChangeDelete:
                NSLog(@"frcChangeDelete");
                [self.tableView deleteRowsAtIndexPaths:@[modifiedIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
    
            case NSFetchedResultsChangeUpdate:
                NSLog(@"frcChangeUpdate");
                [self.tableView reloadRowsAtIndexPaths:@[modifiedIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
    
            case NSFetchedResultsChangeMove:
                NSLog(@"frcChangeDelete");
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:modifiedIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:modifiedNewIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
        }
    
    }
    

    我希望这对某人有帮助!

    【讨论】:

    【解决方案2】:

    在您的 ThreeFRC 项目中存在一些问题:

    - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
        [self.tableView beginUpdates];
        self.numberOfSectionsInTV = 0;
        [self fetchData];
    }
    
    - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
        [self.tableView reloadData];
        [self.tableView endUpdates];
    }
    

    您不应该在 FRC 委托中使用 fetchData。方法以正确的顺序(更新之前、更新期间和更新之后)被调用,因此在回调中你有一致的上下文状态。此外,在 endUpdates 之前使用 reloadData 也不是最好的主意(它正在应用您之前提供的所有更改)并且 reloadData 正在擦除所有内容并从头开始构建它。这很可能导致崩溃。

    我发现的其他可能有问题的事情是处理更新。如果您有 3 个没有部分的单独 FRC,您将不会在 FRC 委托中获得部分更新回调。但是,如果某些对象出现在其中一个 FRC 中,那么您应该检测到并手动插入它们。

    仅在 controllerDidChangeContent 中使用 reloadData 就足够了,但这不是最佳解决方案,因为您不会获得任何动画。正确的方法是处理所有情况:从一个 FRC 中删除所有对象(然后从 TableView 中手动删除部分),将第一个对象插入 FRC(然后您应该在正确的 indexPath 处创建新部分)。

    【讨论】:

    • 所以,这消除了行错误;但是,您是对的,我遇到了插入错误。请参阅问题中的编辑。您能否详细说明如何从 FRC 中删除对象并手动插入它们?是否只是将其设置为nil 并重新初始化 FRC?我有 3 个单独的 FRC,每个部分一个。我试图更改controllerWillChangeContent:controller 中的部分编号,但没有奏效。请在此处查看我的代码更改:github.com/KausiAhmed/ThreeFRC/blob/master/FRC/FRCTableVC.m 谢谢!
    • 如果存在部分,我现在可以插入对象。我不知道如何添加或删除部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-08
    • 2013-04-23
    相关资源
    最近更新 更多