NSFetchedResultsController: Multiple FRCs, Delegate Error when Updating

前端 未结 2 1819
孤城傲影
孤城傲影 2020-12-12 06:21

Objective: Using FRC, sort Section\'s by startDate, an NSDate attribute, but want Today\'s date Se

相关标签:
2条回答
  • 2020-12-12 06:47

    In your ThreeFRC project there are some issues:

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

    You shouldn't use fetchData inside FRC delegate. Methods are called in proper order (before, during and after update) so inside callbacks you have consistent state of context. Also it's not the best idea to use reloadData before endUpdates(it's applying all changes you provided earlier) and reloadData is erasing everything and building it from scratch. This is most likely causing the crash.

    Other thing I've spotted that may be buggy is handling of updates. If you have 3 separate FRC without sections you won't get section update callback in FRC delegate. But if some objects appear in one of the FRC's then you should detect that and manually insert them.

    Using just reloadData in controllerDidChangeContent would be enough, but this isn't the best solution, as you won't get any animations. The proper way would be to handle all the cases: deleting all objects from one of FRCs (and then deleting section manually from TableView), inserting first object into FRC (then you should create new section at proper indexPath).

    0 讨论(0)
  • 2020-12-12 06:52

    i looked at your ThreeFRC project and noticed that in - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView you check which FRCs contain objects and that would determine the number of sections. this makes logical sense, but really confuses the FRC delegate when adding/deleting "sections" (or, when your other FRCs suddenly have objects). For example, you only have a Past section (1 section), but then the data changes such that you now also have a Today section. Since sectionPastFRC or the other FRCs didn't have any section changes, there are no calls to - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type, and though you should have 2 sections now, there were no calls to add, delete, or move sections. you'd have to update the sections manually somehow, which may be a pain.

    here's the workaround i suggest: since you will ALWAYS have at most one section for each FRC, you should just return 3 in - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView. This is so there will no longer be any problem in adding/deleting a section because they were all already there. Anyway, if, for example, the Today section has no objects, just return 0 in - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section. Just make sure that in - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section, if fetchedObjects==0, return nil, so that it also won't display the section header if that section has no objects. And in your FRC delegate didChangeObject, just always adjust the indexPath and newIndexPath before performing changes on the tableView.

    note that this workaround will only work if you already know the maximum number of sections that the FRCs (except the last FRC) will need. it is NOT a solution for all implementations of multiple FRCs in a single table view. i've actually used this solution in a project where i had 2 FRCs for one tableView, but the first FRC would only always take up 1 section, while the second FRC could have any number of sections. i always just had to adjust the sections +1 for changes in the second FRC.

    i've actually tried applying the changes i mentioned above into your code, and haven't been getting errors. here are the parts i changed in the 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;
    }
    

    and for the 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;
        }
    
    }
    

    i hope this helps someone!

    0 讨论(0)
提交回复
热议问题