8

Edit: The solution for this answer is related to iOS7 sometimes returning NSIndexPath and other times returning NSMutableIndexPath. The issue wasn't really related to begin/endUpdates, but hopefully the solution will help some others.


All - I'm running my app on iOS 7, and I'm running into problems with the beginUpdates and endUpdates methods for a UITableView.

I have a tableview that needs to change the height of a cell when touched. Below is my code:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

        // If our cell is selected, return double height
        if([self cellIsSelected:indexPath]) {
            return 117;
        }

        // Cell isn't selected so return single height
        return 58;

}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

        ChecklistItemCell *cell = (ChecklistItemCell *)[self.tableview cellForRowAtIndexPath:indexPath];
        [cell.decreaseButton setHidden:NO];
        [cell.increaseButton setHidden:NO];

        // Toggle 'selected' state
        BOOL isSelected = ![self cellIsSelected:indexPath];

        DLog(@"%@", selectedIndexes);

        DLog(@"is selected: %@", isSelected ? @"yes":@"no");
        // Store cell 'selected' state keyed on indexPath
        NSNumber *selectedIndex = @(isSelected);
        selectedIndexes[indexPath] = selectedIndex;

        [tableView beginUpdates];
        [tableView endUpdates];

}

The beginUpdates and endUpdates methods are working pretty inconsistently. The didSelectRowAtIndexPath method gets called correctly on each touch(I thought at first the UI was getting blocked), and the selectedIndexes is storing alternating values correctly. The issue is, sometimes I touch a table cell and all the methods are called properly, but the cell height doesn't change. Anyone know what's going on?

coder
  • 10,460
  • 17
  • 72
  • 125

2 Answers2

21

There is a change in behavior in iOS7 where index paths are sometimes instances of NSIndexPath and other times UIMutableIndexPath. The problem is that isEqual between these two classes is always going to return NO. Thus, you cannot reliably use index paths as dictionary keys or in other scenarios that rely on isEqual.

I can think of a couple of viable solutions:

  1. Write a method that always returns an instance of NSIndexPath and use it to generate keys:

    - (NSIndexPath *)keyForIndexPath:(NSIndexPath *)indexPath
    {
        if ([indexPath class] == [NSIndexPath class]) {
            return indexPath;
        }
        return [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
    }
    
  2. Identify rows by the data, not the index path. For example, if your data model is an array of NSString, use that string as the key into your selectedIndexes map. If your data model is an array of NSManagedObjects, use the objectID, etc.

I'm successfully using both of these solutions in my code.

EDIT Modified solution (1) based on @rob's suggestion of returning NSIndexPaths rather than NSStrings.

Timothy Moose
  • 9,895
  • 3
  • 33
  • 44
  • 2
    I applied this solution by making a "strict" indexPath and using that in "forKey" lookups. E.g.: NSIndexPath* strictIndexPath = [NSIndexPath indexPathForItem:indexPath.row inSection:indexPath.section]; – Rob Sep 21 '13 at 03:44
  • @rob I like that. Updated my answer with a slightly modified variation. – Timothy Moose Sep 21 '13 at 04:26
  • I'm running into the same issue. But I don't understand where to apply @TimothyMoose 's `keyForIndexPath` inside the original posters code. Where would we call this method? – Brett Sep 21 '13 at 11:55
  • @Brett The original poster would call the method anytime he reads or writes to the `selectedIndexes` dictionary. For example, instead of `selectedIndexes[indexPath]` he'd do `selectedIndexes[[self keyForIndexPath indexPath]]` – Timothy Moose Sep 21 '13 at 15:51
  • i have the same problem as @workInAFishBowl but when i checked to see fi [indexPath class] == [NSIndexPath class] would return true or false it always returned true no matter what.. seems like your answer didnt work for me, any thoughts? – newton_guima Sep 28 '13 at 16:16
  • 1
    @MrAppleBR I've tested this fix code extensively, so I think you've got some other issue. But could you just try removing the `if` statement entirely? If it still doesn't work, you might want to post a new question and include your view controller code. – Timothy Moose Sep 28 '13 at 19:25
  • @TimothyMoose it actually worked.. i just removed the if like you said and now its good to go..!! also, are you guys having problems when deleting rows? – newton_guima Sep 29 '13 at 14:36
  • @MrAppleBR no problems deleting rows here. All of our stuff is based on my [TLIndexPathTools](http://tlindexpathtools.com) library if you're interested. There are several sample projects that delete rows (Collapse, Outline & Settings off the top of my head). – Timothy Moose Sep 29 '13 at 17:37
  • Just curious, how did you find this? Did you read in docs? Testing extensively?... – henrique Jun 18 '19 at 20:02
  • @valcanaia just inspected object types in the debugger. – Timothy Moose Sep 20 '19 at 19:03
0

endUpdates shouldn't be called immediately after beginUpdates. The latter's documentation states, "Begin a series of method calls that insert, delete, or select rows and sections of the receiver." That suggests that it should be called in willSelectRowAtIndexPath: and endUpdates should be called in didSelectRowAtIndexPath.

NRitH
  • 13,441
  • 4
  • 41
  • 44
  • 1
    What he's doing has been a well-known trick for animating a cell's row height in prior versions of iOS. – Timothy Moose Sep 20 '13 at 15:21
  • @TimothyMoose you're assumption is correct. Do you know why it isn't working in iOS7 or do you know work-arounds? – coder Sep 20 '13 at 15:29