1

我有 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) 被调用并且它正确绘制(带有圆顶角)但是单元格在它(以前在顶部)仍然带有圆形顶角,我需要以某种方式重新绘制它。

-=已编辑=-

我更改了 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;
    };
}
4

2 回答 2

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
于 2013-05-24T17:48:26.830 回答
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;

}
于 2013-05-25T12:35:47.947 回答