UITableView: Juxtaposing row, header, and footer insertions/deletions

Posted by jdandrea on Stack Overflow See other posts from Stack Overflow or by jdandrea
Published on 2010-05-18T15:27:29Z Indexed on 2010/05/18 19:00 UTC
Read the original article Hit count: 659

Filed under:
|
|
|
|

Consider a very simple UITableView with one of two states.

First state:

  • One (overall) table footer
  • One section containing two rows, a section header, and a section footer

Second state:

  • No table footer
  • One section containing four rows and no section header/footer

In both cases, each row is essentially one of four possible UITableViewCell objects, each containing its own UITextField. We don't even bother with reuse or caching, since we're only dealing with four known cells in this case. They've been created in an accompanying XIB, so we already have them all wired up and ready to go.

Now consider we want to toggle between the two states.

Sounds easy enough. Let's suppose our view controller's right bar button item provides the toggling support. We'll also track the current state with an ivar and enumeration.

To be explicit for a sec, here's how one might go from state 1 to 2. (Presume we handle the bar button item's title as well.) In short, we want to clear out our table's footer view, then insert the third and fourth rows. We batch this inside an update block like so:

// Brute forced references to the third and fourth rows in section 0
NSUInteger row02[] = {0, 2};
NSUInteger row03[] = {0, 3};

[self.tableView beginUpdates];
state = tableStateTwo; // 'internal' iVar, not a property
self.tableView.tableFooterView = nil;
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObjects:
    [NSIndexPath indexPathWithIndexes:row02 length:2],
    [NSIndexPath indexPathWithIndexes:row03 length:2], nil]
    withRowAnimation:UITableViewRowAnimationFade];  
[self.tableView endUpdates];

For the reverse, we want to reassign the table footer view (which, like the cells, is in the XIB ready and waiting), and remove the last two rows:

// Use row02 and row03 from earlier snippet

[self.tableView beginUpdates];  
state = tableStateOne;
self.tableView.tableFooterView = theTableFooterView;
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:
    [NSIndexPath indexPathWithIndexes:row02 length:2],
    [NSIndexPath indexPathWithIndexes:row03 length:2], nil]
    withRowAnimation:UITableViewRowAnimationFade];  
[self.tableView endUpdates];

Now, when the table asks for rows, it's very cut and dry. The first two cells are the same in both cases. Only the last two appear/disappear depending on the state. The state ivar is consulted when the Table View asks for things like number of rows in a section, height for header/footer in a section, or view for header/footer in a section.

This last bit is also where I'm running into trouble.

Using the above logic, section 0's header/footer does not disappear. Specifically, the footer stays below the inserted rows, but the header now overlays the topmost row. If we switch back to state one, the section footer is removed, but the section header remains.

How about using [self.tableView reloadData] then? Sure, why not. We take care not to use it inside the update block, per Apple's advisement, and simply add it after endUpdates.

This time, good news! The section 0 header/footer disappears. :)

However ...

Toggling back to state one results in a most exquisite mess! The section 0 header returns, only to overlay the first row once again (instead of appear above it). The section 0 footer is placed below the last row just fine, but the overall table footer - now reinstated - overlays the section footer. Waaaaaah … now what?

Just to be sure, let's toggle back to state two again. Yep, that looks fine. Coming back to state one? Yecccch.

I also tried sprinkling in a few other stunts like using reloadSections:withRowAnimation:, but that only serves to make things worse.

NSRange range = {0, 1};
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];

...

[self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationFade];

Case in point: If we invoke reloadSections... just before the end of the update block, changing to state two hides the first two rows from view, even though the space they would otherwise occupy remains. Switching back to state one returns section 0's header/footer to normal, but those first two rows remain invisible.

Case two: Moving reloadSections... to just after the update block but before reloadData results in all rows becoming invisible! (I refer to the row as being invisible because, during tracing, tableView:cellForRowAtIndexPath: is returning bona-fide cell objects for those rows.)

Case three: Moving reloadSections... after tableView:cellForRowAtIndexPath: brings us a bit closer, but the section 0 header/footer never returns when switching back to state one.

Hmm. Perhaps it's a faux pas using both reloadSections... and reloadData, based on what I'm seeing at trace-time, which brings us to:

Case four: Replacing reloadData with reloadSections... outright. All cells in state two disappear. All cells in state one remain missing as well (though the space is kept).

So much for that theory. :)

Tracing through the code, the cell and view objects, as well as the section heights, are all where they should be at the opportune times. They just aren't rendering sanely.

So, how to crack this case? Clues welcome/appreciated!

© Stack Overflow or respective owner

Related posts about iphone

Related posts about uitableview