10

I've got a project (that has been written by other people) where there's a feed with content and text displayed inside a table view. Each post corresponds to one section in table view, and each section has its own rows corresponding to elements like content, text, like button etc.

I need to display a short label for post captions with a "more" button inside table view cell, and when more button is tapped, the label will expand to whatever size the caption fits, all happening inside a table view cell. When the more button is tapped I change the label's numberOfLines property to zero, and as the cells have automatic height, all I need is to reload that particular caption cell. (the cell displays correctly with the expanded size if I set numberOfLines to 0 at the first place before displaying the cell.)

I've tried:

[tableView beginUpdates]; tableView endUpdates];

I've tried various animation options with:

[tableView reloadRowsAtIndexPaths:@[myPath] withRowAnimation:UITableViewRowAnimation(Bottom,Top,None etc)];

I've tried:

[tableView reloadSections:[NSIndexSet indexSetWithIndex:myPath.section] withRowAnimation:UITableViewRowAnimation(Top,Bottom,None etc)];

But they all yield the same result: the whole table view layout gets messed up: it jumps to another cell, some views go blank, cells overlap each other, video inside cells stop playing, and the label doesn't expand (but refreshes inside itself, e.g. that short preview txt with one line animates from top/bottom etc but doesn't expand).

What might be causing the mess up of the whole table view and how I can reload just one cell correctly with and expansion animation, without messing up the whole layout? I've seen many questions and answers regarding this, but they all recommend the options that I've already tried and explained above.

My app targets iOS 8.0+

UPDATE: Here is the relevant code (with some parts regarding inner workings that aren't related to layout, removed):

MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier: MyCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.indexPathToReloadOnAnimation = indexPath;
cell.shouldShortenCaption = YES;

id post = self.posts[indexPath.section] ;
[cell setPost:post];

return cell;

And inside setPost::

if(self.shouldShortenCaption){
    captionLabel.numberOfLines = 2;
}else{
    captionLabel.numberOfLines = 0;
}
NSString *text = [some calculated (deterministic) text from post object];

Button action is simple:

self.shouldShortenCaption = NO;
[one of the reload codes that I've written above in the question]

UPDATE 2: Here are some more methods regarding the issue:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    if (section < self.posts.count) {
        return 59;
    }else
        return 0;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    if (section < self.posts.count) {
        MyFeedHeader *header = [tableView dequeueReusableCellWithIdentifier:MyFeedHeaderIdentifier];
        header.delegate = self;
        [header setPostIndex:section];
        [header setPost:self.posts[section]] ;

        return header;
    }
    else
        return nil;
}

Header's setPost: method basically sets the relevant texts to labels (which have nothing to do with the caption, they are completely different cells. the problematic cell is not the header cell). The table doesn't have any footer methods. The only method regarding height is the one above.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section >= self.posts.count) {
        return 1;
    }
    id post = self.posts[section];
    [calculate number of rows, which is deterministic]
    return [number of rows];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    BOOL hasCursor = self.cursor && self.hasMore ? 1 : 0;
    return self.posts.count + hasCursor;
}

(Post count and cursor and hasMore are also deterministic).

UPDATE 3: I've asked a question (which wasn't a duplicate, even though there are similar questions) and got a useful answer that solved my problem. Can the downvoters please elaborate the reason that they've downvoted?

Can Poyrazoğlu
  • 33,241
  • 48
  • 191
  • 389
  • 1
    Can we see cellForRow... and the button action code? – danh Mar 29 '17 at 14:47
  • Also, any methods from `UITableViewDataSource` and `UITableViewDelegat` could be helpfull. – Losiowaty Mar 29 '17 at 14:48
  • @danh I've added it – Can Poyrazoğlu Mar 29 '17 at 15:16
  • @Losiowaty I've added the cellForRow code. for the delegate, what method(s) do you exactly want? the code is quite complicated and 1000+ lines in a single file (as I've said, it's not the code that I wrote), it's both impractical and illegal to post the whole file here. i can add relevant methods if you ask me which one you need though. – Can Poyrazoğlu Mar 29 '17 at 15:17
  • Well, I was thinking about `viewForHeader/FooterInSection:` if you have them. Since you have automatic height, there most likely is no `heightForRowAtIndexPath` ;) maybe `numberOfSection` and `numberOfRows`, if something non-trivial is happening there. – Losiowaty Mar 29 '17 at 15:34
  • @Losiowaty I've added all the relevant parts, hope it helps. – Can Poyrazoğlu Mar 29 '17 at 15:46

1 Answers1

23

Here is an example: https://github.com/DonMag/DynamicCellHeight

Table B is one way of accomplishing "More/Less" (Table A was for another layout I played around with). It uses the [tableView beginUpdates]; tableView endUpdates]; method of triggering the table re-layout.

The key is getting all your constraints set up correctly, so the Auto-Layout engine does what you expect.

The example is in Swift, but should be really easily translated back to Obj-C (I think I did it in Obj-C first).

enter image description here

Edit: some additional notes...

This is using a pretty standard method of dynamic-height table view cells. The vertical spacing constraints between elements effectively "pushes out" the bounds of the cell. The tap here toggles the numberOfLines property of the label between 2 and 0 (zero meaning as many lines as necessary). Sequential calls to beginUpdates / endUpdates on the table view tells the auto-layout engine to recalculate the row heights without needing to reload the data.

For this example, I did use a little "trickery" to get the smooth expand/collapse effect... The multiline label you see here is contained in a UIView (with clipsToBounds true). There is a second, duplicate multiline label (alpha 0 so it's not visible) that is controlling the height. I found that changing the numberOfLines on the visible label sort of "snapped" to 2 lines, and then the size change animation took place... resulting in the text "jumping around."

Just for the heck of it, I added the "not so good" version to my GitHub repo for comparison's sake.

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • While I don't expect a github link to go dead in the forseeable future, could you elaborate on "getting all your constraints setup correctly"? – Losiowaty Mar 29 '17 at 14:50
  • @Losiowaty The cell height must be completely determined from the inside out by the constraints of its contents. – matt Mar 29 '17 at 14:51
  • @Losiowaty - yeah, tough to decide how much explanation to include... There are plenty of tutorials / walkthroughs out there explaining dynamic cell height. This is just building on the "standard" way of doing so. But I'll edit my answer with a little more detail. – DonMag Mar 29 '17 at 15:00
  • I don't think there's a problem with autolayout, as table view is already set to automatic cell height, and the cell does have correct height in each case. the trouble starts when I try to animate from one state to the other though. – Can Poyrazoğlu Mar 29 '17 at 15:05
  • @CanPoyrazoğlu Then look at your code and look at DonMag's code. Whatever the difference is, that's what you are doing wrong. – matt Mar 29 '17 at 15:46
  • @matt well that's what I'm trying to do. it's not always easy to understand someone else's messed up code and try to fix their errors, especially when you've tried everything you've seen on SO :) I've asked the question so that I can see something obvious that I'm not seeing yet, if any. – Can Poyrazoğlu Mar 29 '17 at 15:49
  • @CanPoyrazoğlu DonMag has handed you the answer on a silver platter. Time for you now to learn and think. - Here's another approach: https://github.com/mattneub/variableRowHeights – matt Mar 29 '17 at 15:52
  • @matt I wasn't complaining, I was just explaining the situation from my side. I'll have a look for sure :) – Can Poyrazoğlu Mar 29 '17 at 17:57
  • This *almost* solved the problem. I've implemented it and caused a few more errors with the code I've been given, and fixed them all. However there's still a weird issue: when I expand a row while I'm scrolling down, it's perfect. However, if I'm scrolling up in the feed and expand a row, the animation is glitchy and when I'm going up, it "jumps" to beginning of the cell back after scrolling some offset, creating a visually extremely unpleasing effect. However, I'm not keeping a track of which cell was expanded so I assume it's up to that. – Can Poyrazoğlu Mar 29 '17 at 20:23