NSFetchedResultsController的委托方法处理

时间:2013-05-24 17:33:24

标签: iphone ios objective-c core-data nsfetchedresultscontroller

我有UITableView,附带NSFetchedResultsController。 FRC的委托方法工作得很好,一切都很好,但我的单元格具有依赖于单元格位置的自定义背景: 1.在UITableView的顶部(顶部有圆角的背景) 2.中间(背景没有圆角) 3.在底部(底部有圆角的背景) 4.单细胞(所有角都是圆形的)。 在我的单元格配置方法中,我计算单元格的位置,并为其设置适当的背景视图。一切正常,但FRC的委托方法的实现存在问题:

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

    UITableView *tableView =  self.agenciesTableView;

    switch(type)
    {
        case NSFetchedResultsChangeInsert:
        {
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationNone];
            break;
        }
        case NSFetchedResultsChangeDelete:
        {
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationNone];
            break;
        }
        case NSFetchedResultsChangeUpdate:
        {
            [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

            break;
        }
        case NSFetchedResultsChangeMove:
        {

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
        }
    };
}

正如您所看到的那样,它很常见,但有一个问题: 如果单元格被插入到UITableView的顶部,它的配置方法(cellForRowAtIndexPath)被调用并且它正确绘制(带有圆角顶角)但是它下面的单元格(之前位于顶部)仍然具有圆角顶角和我需要以某种方式重绘它。

- = = EDITED -

我更改了FRC的委托方法以反映更新逻辑:

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

    UITableView *tableView =  self.agenciesTableView;

    switch(type)
    {
        case NSFetchedResultsChangeInsert:
        {
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationNone];

            //if we inserting a cell not to the middle of table view
            //we need to update the previous cell (if we are inserting to the end)
            // or the next cell (if we are inserting to the beggining)
            if ([self controller:controller positionForCellAtIndexPath:newIndexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row+1) inSection:newIndexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row-1) inSection:newIndexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:nextIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
            };
            break;
        }
        case NSFetchedResultsChangeDelete:
        {
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationNone];

            //if we deleting a cell not fromo the middle of table view
            //we need to update the previous cell (if we are deleting from the end)
            //or the next cell (if we are deleting from the beggining)
            if ([self controller:controller positionForCellAtIndexPath:indexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(indexPath.row+1) inSection:indexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(indexPath.row-1) inSection:indexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:nextIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
            };
            break;
        }
        case NSFetchedResultsChangeUpdate:
        {
            [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];

            break;
        }
        case NSFetchedResultsChangeMove:
        {

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationNone];
            if ([self controller:controller positionForCellAtIndexPath:indexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(indexPath.row+1) inSection:indexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(indexPath.row-1) inSection:indexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:nextIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
            };


            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationNone];
            if ([self controller:controller positionForCellAtIndexPath:newIndexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row+1) inSection:newIndexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row-1) inSection:newIndexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:nextIndexPath] withRowAnimation:UITableViewRowAnimationNone];
                };
            };
            break;
        }
    };
}

但在这种情况下,我收到了一个错误:

2013-05-24 21:04:19.458 PharmaTouch[6994:fb03] *** Assertion failure in -[_UITableViewUpdateSupport _computeRowUpdates], /SourceCache/UIKit_Sim/UIKit-  1912.3/UITableViewSupport.m:386
2013-05-24 21:04:19.483 PharmaTouch[6994:fb03] CoreData: error: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  Invalid table view update.  The application has requested an update to the table view that is inconsistent with the state provided by the data source. with userInfo (null)

- = EDITED2 = - 这是我的控制器:cellExistsAtIndexPath:方法实现。它用于安全检查,以避免使用超出范围的indexPath参数进行reloadRowsAtIndexPaths调用。

-(BOOL)controller:(NSFetchedResultsController *)controller cellExistsAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row<0)
    {
        return NO;
    };

    NSInteger numberOfRows = 0;
    NSArray *sections = controller.sections;
    if(sections.count > 0)
    {
        id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:indexPath.section];
        numberOfRows = [sectionInfo numberOfObjects];
    };
    if (indexPath.row>(numberOfRows-1))
    {
        return NO;
    }
    else
    {
        return YES;
    };
}

2 个答案:

答案 0 :(得分:2)

当您使用UITableViewRowAnimationNone时,您可能只是reloadData,然后它将正确完成,因为所有行都将被刷新。

您使用UITableViewRowAnimationFade的位置,请考虑您对它的重视程度......

您需要在检查和重新加载时更全面一些。特别是,您需要从FRC获取有关正在更改的部分的部分信息。如果传入或传出的行是第一行或最后一行,则需要刷新多行。沿着:

if first is changing and count > 1, also refresh second
if last is changing and count > 1, also refresh first

答案 1 :(得分:0)

我通过添加属性@property (nonatomic, retain) NSMutableSet *indexPathsForRowsToReloadAfterFRCUpdates;来解决问题,以保持需要在controllerWillChangeContent:controllerDidChangeContent:方法调用之间更新的行(在FRC委托更新周期中)。

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    self.indexPathsForRowsToReloadAfterFRCUpdates=[[NSMutableSet alloc] init];
    [self.agenciesTableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{

    UITableView *tableView =  self.agenciesTableView;

    switch(type)
    {
        case NSFetchedResultsChangeInsert:
        {
            [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                                  withRowAnimation:UITableViewRowAnimationFade];
            break;
        }
        case NSFetchedResultsChangeDelete:
        {
            [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                                  withRowAnimation:UITableViewRowAnimationFade];
            break;
        }
    };
}


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

    UITableView *tableView =  self.agenciesTableView;

    switch(type)
    {
        case NSFetchedResultsChangeInsert:
        {
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationNone];

            //if we inserting a cell not to the middle of table view
            //we need to update the previous cell (if we are inserting to the end)
            // or the next cell (if we are inserting to the beggining)
            if ([self controller:controller positionForCellAtIndexPath:newIndexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row-1) inSection:newIndexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row+1) inSection:newIndexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:prevIndexPath];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:nextIndexPath];
                };
            };
            break;
        }
        case NSFetchedResultsChangeDelete:
        {
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationNone];

            //if we deleting a cell not fromo the middle of table view
            //we need to update the previous cell (if we are deleting from the end)
            //or the next cell (if we are deleting from the beggining)
            if ([self controller:controller positionForCellAtIndexPath:indexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(indexPath.row) inSection:indexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(indexPath.row+1) inSection:indexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:prevIndexPath];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:nextIndexPath];
                };
            };
            break;
        }
        case NSFetchedResultsChangeUpdate:
        {
            [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];

            break;
        }
        case NSFetchedResultsChangeMove:
        {
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationNone];

            if ([self controller:controller positionForCellAtIndexPath:indexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(indexPath.row) inSection:indexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(indexPath.row+1) inSection:indexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:prevIndexPath];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:nextIndexPath];
                };
            };


            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationNone];

            if ([self controller:controller positionForCellAtIndexPath:newIndexPath]!=UITableViewCellPositionMiddle)
            {
                NSIndexPath *prevIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row-1) inSection:newIndexPath.section];
                NSIndexPath *nextIndexPath=[NSIndexPath indexPathForRow:(newIndexPath.row+1) inSection:newIndexPath.section];
                if ([self controller:controller cellExistsAtIndexPath:prevIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:prevIndexPath];
                };
                if ([self controller:controller cellExistsAtIndexPath:nextIndexPath])
                {
                    [self.indexPathsForRowsToReloadAfterFRCUpdates addObject:nextIndexPath];
                };
            };

            break;
        }
    };
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.agenciesTableView endUpdates];


    if ([self.indexPathsForRowsToReloadAfterFRCUpdates count]>0)
    {
        [self.agenciesTableView reloadRowsAtIndexPaths:[[self.indexPathsForRowsToReloadAfterFRCUpdates allObjects] copy] withRowAnimation:UITableViewRowAnimationNone];
    };
    self.indexPathsForRowsToReloadAfterFRCUpdates=nil;

}