17

I have cells that expand by changing their height with a setExpanded: method call.

I then call reloadRowsAtIndexPaths: to refresh the cells.

The problem is the cells simply disappear and randomly re-appear. I suspect this has to due with the way the indexing is working.

If I call reloadData or beginUpdates/endUpdates the cells work as expected, but I lose the animations.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    JVCell *cell = (JVCell*)[tableView cellForRowAtIndexPath:indexPath];
    JVCell *previousCell = nil;

    if( previousIndexPath_ != nil ) // set to nil in viewDidLoad
    {
        previousCell = (JVCell*)[tableView cellForRowAtIndexPath:previousIndexPath_];
    }


    // expand or collapse cell if it's the same one as last time
    if( previousCell != nil && [previousIndexPath_ compare:indexPath] == NSOrderedSame && [previousCell expandable] )
    {
        [previousCell setExpanded:![cell expanded]];
        NSArray *indexPathArray = [NSArray arrayWithObject:previousIndexPath_];
        [tableView reloadRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationAutomatic];
    }
    else
    {
        // collapse previous cell
        if( previousCell != nil && [previousCell expandable] )
        {
            if( [previousCell expanded] ) [previousCell setExpanded:NO];
            NSArray *indexPathArray = [NSArray arrayWithObject:previousIndexPath_];
            [tableView reloadRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationAutomatic];
        }

        // expand new cell
        if( [cell expandable] )
        {
            [cell setExpanded:YES];
            NSArray *indexPathArray = [NSArray arrayWithObject:indexPath];
            [tableView reloadRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationAutomatic];
        }
    }

    previousIndexPath_ = indexPath;

    // works as expected, but I lose the animations
    //[tableView reloadData];

    // works as expected, but I lose the animations
    //[tableView beginUpdates];
    //[tableView endUpdates];
}

EDIT: updated to include cellForRowAtIndexPath and heightForRowAtIndexPath methods:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];
    JVCellSectionData *sectionData = [sections_ objectAtIndex:section]; // NSArray of SectionData objects
    NSArray *cellArray = [sectionData cells]; // NSArray of cells
    UITableViewCell *cell = [cellArray objectAtIndex:row];
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];
    JVCellSectionData *sectionData = [sections_ objectAtIndex:section];
    NSArray *cellArray = [sectionData cells];
    JVCell *cell = [cellArray objectAtIndex:row];
    return [cell cellHeight]; // changed when selected with setExpanded: method
}

Edit 2: I made a Quicktime video of what was happening and extracted screen shots.

What I'm attempting to do is expand a cell, not replace, insert or delete cells. Each cell has one or more subviews. The height of the cell changes depending on whether it's 'expanded' or not. The content view has the subviews added to it, and it's clipToBounds property is YES. When the cells expands or collapses the height value changes along with the frame of the cell (including background view and selected background view). I've logged all the frame values before, during and after expansion, and they are all consistent with their state and position.

sectioned table view design

cell tapped

cell expanding

cell disappeared

cell retracts

cell visible again

Keep in mind that this works normally on iOS 4.3, as shown below:

comaprison ios 4 to ios 5

TigerCoding
  • 8,710
  • 10
  • 47
  • 72
  • This behaviour is probably due to the fact that although you animate the cell changes you have not necessarily changed the datasource to reflect the changes. So when a reload occurs the cells are recreated as they were originally (changes seem to disappear) – Damo Feb 22 '12 at 13:54
  • I'm changing height and visual appearance in the setExpanded: method. In that method, I recalculate the height, then set the frame of the cell (and subviews) as needed. Is there something else I can do? – TigerCoding Feb 22 '12 at 13:57
  • Things to check 1. cellForRowAtIndexPath: method & 2. calls to reloadData. What I meant by my original comment was - if cellForRowAtIndexPath: is called subsequent to your animation, will the cells be created a) as they were or b) as you wish them to be? i.e. if the data which is used to create the cells in the first place is not changing then when a reload occurs any animated changes you have made will 'disappear'. – Damo Feb 22 '12 at 15:17
  • Why aren't you using the delegate's method: - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath ? – onnoweb Feb 22 '12 at 15:35
  • @Damo I pre-create all the cells in viewDidLoad. They are not created nor re-used in cellForRowAtIndexPath. the only thing that changes is the height. Each cell has one or more sub-views that are clipped depending on cell height. Interestingly enough, when tapping cells that don't expand logging always shows the correct section/row numbers, but the moment I tap an expanding cell, then tap another cell, the section number is incorrect. – TigerCoding Feb 22 '12 at 16:58
  • @onnoweb that method is implemented, and calls the subclassed UITableViewCell's 'cellHeight' method, which was calculated in the code above once the cell was tapped. – TigerCoding Feb 22 '12 at 16:59
  • I have to say though - this is the weirdest way I've seen a table view populated :-D – Damo Feb 23 '12 at 08:30
  • Yes, but when (if) I get it working, it will be very nice. – TigerCoding Feb 23 '12 at 11:39
  • Hi Javy i have alike code of cell expansion i can share with u the source code plz give me ur mail id – Dhara Mar 03 '12 at 06:58
  • Can you expand on "the section number is incorrect"? – Magnus Mar 03 '12 at 12:19
  • @Dhara can you upload your code here as an answer and I can test it out? The important thing is it should have an image that expands with the cell as its height changes. – TigerCoding Mar 03 '12 at 13:46
  • @Magnus it would appear I was mistaken about that, or it has stopped doing so. My current logging (with the same source as above) logs the correct section/row that is tapped. – TigerCoding Mar 03 '12 at 13:47
  • @Javy i have a code in which there is a table view section.On clicking the section all the rows inside it expands – Dhara Mar 05 '12 at 05:58

7 Answers7

7

I don't see any difference in behavior between iOS 5.0 and 4.3.2 in the simulator, or 5.0 on my phone—the cells disappear in the same way on each. (I'd test on 4.2.1 on my older iPod touch but Xcode 4 can't. Grr..) It looks like there's an easy workaround, though: If you do both reloadRowsAtIndexPaths:withRowAnimation: after expanding/collapsing your cells and then the reloadData call after that, it appears to preserve the animation.

Here's a small demo project for this. It's hard to say what the actual problem was—it's just some odd side effect of how UIKit is doing the cell animation. If you subclass [UITableViewCell setAlpha:] you can see that the table view is setting the cell's alpha to 0 and leaving it there. Weird.

Edit: Well that's weird. It wasn't working for a bit (was doing the old behavior, with cells disappearing), but now it's right again. Maybe I was running the wrong version. Anyway, let me know if this works for you.

davehayden
  • 3,484
  • 21
  • 28
  • The reload data works, but the animation is unfortunately kind of funky. However, if I override the setAlpha method as you suggested, and don't allow it to set the alpha, the problem appears to go away. I can't imagine WHY the cells would be disappearing. – TigerCoding Feb 28 '12 at 11:40
  • It sounds like there's something else going on. Calling reloadData or calling beginUpdates and endUpdates seems to work okay for me, as in [this SO post](http://stackoverflow.com/questions/460014/can-you-animate-a-height-change-on-a-uitableviewcell-when-selected). Are you using any other UITableView delegate methods to change the way things appear? Is your code not working like the demo I posted above, and if so, can you figure out where it's diverging in the code? If the sample is working, try merging your code in bit by bit to see what's causing it to act differently. – davehayden Feb 28 '12 at 20:05
  • I tested your code, and it does work as you said. Did you have a problem with your code before? You said, "Well that's weird. It wasn't working for a bit..." It will always fails for me unless I override the alpha. I followed the thread in the debugger and it went from "reloadRowsAtIndexPaths:withAnimation:" to a private table view method (update I think) then right to setAlpha, so it isn't calling any other delegate methods. I will try removing some of the content from my cell-subclass (it's rather complicated) to identify the culprit. – TigerCoding Feb 29 '12 at 12:14
  • The weird bit was just that it was working and then suddenly it wasn't and then it was again. But I think I just lost track of what I was poking around at. :) – davehayden Mar 02 '12 at 04:31
  • Any chance you can add 3 images as subviews to your cells, one on top of the other height-wise, and have the cell expand, revealing or hiding the images as this happens? I think with simple text it works but when an image is used it may cause problems. I'm assigning stretchable images ot both backgroundView and selectedBackgroundview (by setting them to an image view when they are created). – TigerCoding Mar 03 '12 at 13:51
  • What problems are you having? I put images in and a stretchable background, seems to work okay: http://dl.dropbox.com/u/11290499/table.zip – davehayden Mar 04 '12 at 00:54
  • Your code works perfectly, so I know my cells are doing something that is causing it. I will compare the two versions and try to find the problem. I think it may be because I'm layout out my subviews each time (while your code doesn't) or because my background image changes, but I think that's unlikely. Thank you very much for all your help, at least now I have something to compare my problem to. – TigerCoding Mar 04 '12 at 10:52
  • I see the difference in your code and mine. I'm using a sub-class of UITableViewCell that has several sub-classed UIView objects as subviews. Your implemetation expands the cell height while mine has multiple UIViews that extend beyond the cells height. Because of this your can't be used as a reference. Is there any chance you could find the time to add 3 subviews to a subclass of UITableViewCell and make it expand correctly? If I can get it working I will certainly post the solution as an edit to the original question. Thanks! – TigerCoding Mar 04 '12 at 14:15
  • I stand corrected, I see what you did with your code now. Thanks Dave! – TigerCoding Mar 05 '12 at 09:13
  • Great! Glad to hear you got it working, and glad I could help. :) – davehayden Mar 06 '12 at 00:56
  • BTW, if you remove the reloadData statement from your code and rely entirely on the reloadRowsAtIndexPaths method, then the problem occurs again. I don't know why this happens in iOS 5. I do have my code working, but I kind of hate the hacky feel of having to call reloadData, but at least it works. :) – TigerCoding Mar 06 '12 at 16:39
  • I had to mark it as not correct. Apologies, but when I use reloadData (as in your code), it pretty much ruins the animations. I think its slightly smoother for your example because your cell has simpler subviews. On the other hand, the whole use of reloadRowsAtIndexPaths fails because the cells do not show the expand and collapse correctly. In fact, it does just the opposite. This is very strange as log statements clearly show the frames are correct, but after the log statements, and reloadRowsAtIndexPaths is called, the cell is shown as the complete opposite. – TigerCoding Mar 07 '12 at 15:57
  • I think I'm bipolar today. =O I figured out that it needed beginUpdates and endUpdates to smooth things out. Strange because that was screwing up the animations before. Thankfully this fully resolves everything, working on iOS 4 and 5, as well as keeping all animations intact. – TigerCoding Mar 07 '12 at 16:28
  • It seems there are still problems with it. I created a bare-bones project and linked it in the new question if you want to give it a go: http://stackoverflow.com/questions/9649491/why-do-my-table-view-cells-disappear-when-reloaded-using-reloadrowsatindexpaths – TigerCoding Mar 10 '12 at 19:37
5

I had the same issue. I confirm that using no animation when reloading the row works fine.

But it turns out the issue is caused by returning a cached UITableViewCell in cellForRowAtIndexPath when actually a fresh one is needed.

The doc for reloadRowsAtIndexPaths:withRowAnimation: says: "Reloading a row causes the table view to ask its data source for a new cell for that row."

Your cellForRowAtIndexPath method is returning locally cached cells. Returning a brand new cell fixed the issue in my case and no workarounds were needed...

Dan Mardale
  • 51
  • 1
  • 2
2

@Javy,

Thanks for your question. I was developing table with similar behaviour: there are text views (UITextView) in my table view cells (UITableViewCell) that are :

  1. expanding if new line is required to display text without scrolling, or
  2. collapsing if new line was removed

So I found the same problem. In iOS 5.x when I was starting to type text it suddenly was becoming invisible. But in iOS 4.x everything works fine.

I have found the solution and it works well for me.

Solution: Just try to replace your animation type UITableViewRowAnimationAutomatic with UITableViewRowAnimationNone when reloading the particular cell.

Some additional code: reload both cell in one moment:

    NSMutableArray *indexes = [NSMutableArray arrayWithCapacity:2];
    // collapse previous cell
    if( previousCell != nil && [previousCell expandable] )
    {
        if( [previousCell expanded] ) [previousCell setExpanded:NO];
        [indexes addObject:previousIndexPath_];
    }

    // expand new cell
    if( [cell expandable] )
    {
        [cell setExpanded:YES];
        [indexes addObject:indexPath];
    }

    [tableView reloadRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationNone];

Hope it will help you.

Nekto
  • 17,837
  • 1
  • 55
  • 65
  • changing it keeps the cell from disappearing when it expands, but it still disappears when it collapses. :( – TigerCoding Mar 01 '12 at 13:31
  • @Javy please see my update answer. Try to reload cell that you want to collapse and expand using one call of `reloadRowsAtIndexPaths`. Just save all appropriate indexed in one array and pass array to that method (see my answer). – Nekto Mar 01 '12 at 14:37
  • I had hope for this, as the alpha didn't change when expanding, but I tried the above code and instead the cells have the bottoms cut off of them when collapsing. This seems to only happen in the same section that the cells are in. What's more strange is even if I override the alpha, and prevent it from being zero, it still cuts off the cell bottom (or top). It's as if it's not looking to the new cell size. – TigerCoding Mar 02 '12 at 16:53
  • And what is happening if you will increase height of expandable rows? I don't clear understand what now is wrong. – Nekto Mar 02 '12 at 20:42
  • May be you could try to reload section(s)? They will have animations too – Nekto Mar 02 '12 at 20:45
  • I just tried reloading sections and it goes back to being completely invisible. :( I do change the row heights in the "setExpanded:" expanded method. That changes the frame of the cell backgroundView, selectedBackgroundView, and contentView. – TigerCoding Mar 03 '12 at 13:41
  • Something is wrong in your code. It works perfect in my case. If you want to increase height of cell - then just return another height in `heightForRowAtIndexPath`. Also check `autoresizing` mask. Moreover I think my answer is the most helpful here, so I'll be thankful for accepting. But it is your choice – Nekto Mar 03 '12 at 15:51
  • If you can edit your answer and include your working code, I may award it to you. It needs to have a stretchable background image for the cell. The easiest way to do it is to replace the backgroundView and selectedBackgroundView with an image view property. I can make this work as normal with text only. I think the images are somehow interfering? As I said I'll award you if you can produce a complete working example, but otherwise davehayden provided the tip about overriding alpha which currently resolves the issue, even if it is a hack. – TigerCoding Mar 03 '12 at 18:13
0

In:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

you must return a new cell. If cell you don't need to create a new one, you only need:

[tableView beginUpdates];
[tableView endUpdates];

There is no need to call reloadRowsAtIndexPaths.

atxe
  • 5,029
  • 2
  • 36
  • 50
gaoyp
  • 1
  • Thank you Rajesh, but the culprit was resolved in this question I posted later http://stackoverflow.com/questions/9649491/why-do-my-table-view-cells-disappear-when-reloaded-using-reloadrowsatindexpaths – TigerCoding Apr 14 '12 at 07:01
0

Seems to me like the cell isn't redrawn properly.

Did you try something like:

[cell.backgroundView setNeedsDisplay]
Yedy
  • 2,107
  • 1
  • 25
  • 30
  • Yes, I placed this at the end of layoutSubViews. I beginning to think this is an iOS 5 bug, as it works fine is iOS 4. – TigerCoding Mar 01 '12 at 13:32
  • Hm, might be a timing problem. Would you mind to try the upper code in cellForRowAtIndexPath? – Yedy Mar 01 '12 at 16:31
  • But the entire cell disappears. It seems strange to call this method there. Why refresh the backgroundView? Perhaps something else? – TigerCoding Mar 01 '12 at 17:21
  • `cell setNeedsLayout ` ? Is it always the case that the expanded rows first appear and than fade away? – Yedy Mar 01 '12 at 17:53
  • The row expands, and fades out at the same time. the fading starts right when the expansion start, as if it's part of the animation. – TigerCoding Mar 02 '12 at 16:54
0

you could try reloading the table view when the animations have completed? at the moment if your animations are fired, and then you call [tableView reloadData] the animations won't be complete, so the frame etc may get out of sync

you could change your set expanded method to take a flag as well as an animation completion block, which could contain the reload code

[cell setExpanded:YES completion:^
    //reload code
}];
wattson12
  • 11,176
  • 2
  • 32
  • 34
  • It would still disappear until the reload was called wouldn't it? As mentioned to @Yedi in another answer, I think this is an iOS 5 bug, because there is no reasonable explanation for it. Only workarounds to avoid it. – TigerCoding Mar 01 '12 at 13:34
-1

Okay...

before you

previousIndexPath_ = indexPath;

and after you do all the expanding / contracting of heights etc; you need to do something like

[cellArray replaceObjectAtIndex:previousIndexPath_ withObject:previousCell];
[cellArray replaceObjectAtIndex:indexPath withObject:cell];

which means your cellArray needs to be

NSMutableArray* cellArray;
Damo
  • 12,840
  • 3
  • 51
  • 62
  • Why would the cell array need to be mutable? The cells are not added. removed or re-ordered in any way. I think maybe I have not explained what's happening well enough? Please see my edits above. – TigerCoding Feb 23 '12 at 11:38
  • Mutable so that you can replace the cell objects as above. - you need to update the cellArray as well as update the current cell in the view because reload data looks at the cell array only. The code you have in didSelectRowAtIndexPath takes a cell from the table view itself. but you need to change the cell array also because otherwise you will get the behaviour you are seeing. – Damo Feb 23 '12 at 13:41
  • I'm thankful for your help, but what you've said isn't making much sense to me. I have an array of 'SectionData' objects that hold two strings (header & footer text), and an immutable cell array. The cells never change position. Only the height of the cell changes. Even in this code example http://www.roostersoftstudios.com/2011/04/14/iphone-uitableview-with-animated-expanding-cells/ the author calls reload and doesn't call replace on any of the cells. – TigerCoding Feb 23 '12 at 14:24
  • have you tried adding those few lines yet? If it doesn't work, what have you lost? – Damo Feb 23 '12 at 14:31
  • Followed your advice, changed the arrays to be mutable, implemented your code, and I see it swapping cell positions in the table view. Even so, the initial click on an expanding table view doesn't need to be swapped, but it still disappears. – TigerCoding Feb 23 '12 at 14:50