NSFetchedResultsController: Multiple FRCs, Delegate Error when Updating

前端 未结 2 1837
孤城傲影
孤城傲影 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: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 )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!

提交回复
热议问题