Core Animation Crash: Attempt to create two animations for cell

1 Mar
2010

Google’s not very helpful on this error. NSInternalInconsistencyException ‘Attempt to create two animations for cell’

After banging my head against the wall several times, I got the solution – and how simple it is! I’ll post the full code here.
In short, remember all your insert/update/delete operations and make sure only one animation per cell is called.
Remember this is three20 code, and you’ll need the willUpdateFor- functions in the DataSource. This overrides code from three20′s internal sources.
Update: changed dictionary to set.

// a beginUpdate - endUpdate block is only allowed if the total sum of elements is correct after the update
// so we need to check if view is visible, or else just update on endUpdates.
- (void)beginUpdates {
  if (_isViewAppearing && _flags.isShowingModel) {
    [_tableView beginUpdates];
    updatedRows = [[NSMutableSet set] retain];
  }
}

- (void)endUpdates {
  if (_isViewAppearing && _flags.isShowingModel) {
    [_tableView endUpdates];
    RELEASE(updatedRows);
  }else {
    [_tableView reloadData];
  }
}

- (BOOL)isIndexPathInSet:(NSIndexPath *)aPath {
  if (updatedRows) {
    for(NSIndexPath *path in updatedRows) {
     if ([path compare:aPath] == NSOrderedSame) {
       return YES;
      }
    }
  }
  return NO;
}

- (void)model:(id<TTModel>)model didUpdateObject:(id)object atIndexPath:(NSIndexPath*)indexPath {
  if (model == _model) {
    if (_isViewAppearing && _flags.isShowingModel) {
      if ([_dataSource respondsToSelector:@selector(tableView:willUpdateObject:atIndexPath:)]) {
        NSIndexPath* newIndexPath = [_dataSource tableView:_tableView willUpdateObject:object
                                               atIndexPath:indexPath];
        if (newIndexPath) {
          if (newIndexPath.length == 1) {
            MRDINFO(@"UPDATING SECTION AT %@", newIndexPath);
            NSInteger sectionIndex = [newIndexPath indexAtPosition:0];
            [_tableView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                      withRowAnimation:UITableViewRowAnimationTop];
          } else if (newIndexPath.length == 2) {
            MRDINFO(@"UPDATING ROW AT %@", newIndexPath);
            if (![self isIndexPathInSet:newIndexPath]) {
              [updatedRows addObject:newIndexPath];
              [_tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                                withRowAnimation:UITableViewRowAnimationNone];
            }else {
              // fixes NSInternalInconsistencyException 'Attempt to create two animations for cell'
              MRDINFO(@"prevented multiple updates for %@", newIndexPath);
            }
          }
          [self invalidateView];
        } else {
          [_tableView reloadData];
        }
      }
    } else {
      [self refresh];
    }
  }
}

- (void)model:(id<TTModel>)model didInsertObject:(id)object atIndexPath:(NSIndexPath*)indexPath {
  if (model == _model) {
    if (_isViewAppearing && _flags.isShowingModel) {
      if ([_dataSource respondsToSelector:@selector(tableView:willInsertObject:atIndexPath:)]) {
        NSIndexPath* newIndexPath = [_dataSource tableView:_tableView willInsertObject:object
                                               atIndexPath:indexPath];
        if (newIndexPath) {
          if (newIndexPath.length == 1) {
            MRDINFO(@"INSERTING SECTION AT %@", newIndexPath);
            NSInteger sectionIndex = [newIndexPath indexAtPosition:0];
            [_tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                      withRowAnimation:UITableViewRowAnimationTop];
          } else if (newIndexPath.length == 2) {
            MRDINFO(@"INSERTING ROW AT %@", newIndexPath);
            if (![self isIndexPathInSet:newIndexPath]) {
              [updatedRows addObject:newIndexPath];
              [_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                                withRowAnimation:UITableViewRowAnimationTop];
            }else {
              MRDINFO(@"prevented multiple updates for %@", newIndexPath);
            }    
            // crashes sometimes with index out of bounds. not a good idea at all.
            //[_tableView scrollToRowAtIndexPath:newIndexPath
            //                  atScrollPosition:UITableViewScrollPositionNone animated:NO];
          }
          [self invalidateView];
        } else {
          [_tableView reloadData];
        }
      }
    } else {
      [self refresh];
    }
  }
}

- (void)model:(id<TTModel>)model didDeleteObject:(id)object atIndexPath:(NSIndexPath*)indexPath {
  if (model == _model) {
    if (_isViewAppearing && _flags.isShowingModel) {
      if ([_dataSource respondsToSelector:@selector(tableView:willRemoveObject:atIndexPath:)]) {
        NSIndexPath* newIndexPath = [_dataSource tableView:_tableView willRemoveObject:object
                                               atIndexPath:indexPath];
        if (newIndexPath) {
          if (newIndexPath.length == 1) {
            MRDINFO(@"DELETING SECTION AT %@", newIndexPath);
            NSInteger sectionIndex = [newIndexPath indexAtPosition:0];
            [_tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                      withRowAnimation:UITableViewRowAnimationLeft];
          } else if (newIndexPath.length == 2) {
            MRDINFO(@"DELETING ROW AT %@", newIndexPath);
            if (![self isIndexPathInSet:newIndexPath]) {
              [updatedRows addObject:newIndexPath];
              [_tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                                withRowAnimation:UITableViewRowAnimationLeft];
            }else {
              MRDINFO(@"prevented multiple updates for %@", newIndexPath);
            }
          }
          [self invalidateView];
        } else {
          [_tableView reloadData];
        }
      }
    } else {
      [self refresh];
    }
  }
}

Related posts:

  1. UIScrollView: detect if we are at bottom
  2. Debugging Core Data
  3. Core Data Notes from iPhone Tech Talk
  4. Tweetie like swipe menu for iPhone apps
  5. Crash Reports on the iPhone

Comment Form

top

Switch to our mobile site