Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I have a UITableViewCell subclass which has an image, title and description. I am supposed to resize the cell height according to the description content length i.e. if it spans more than 5 lines, I should extend it (+other subviews like image etc) till it lasts.

The next coming cells should begin only after that.

I have my UITableViewCell subclass instantiated from xib which has a fixed row height = 160.

I know this is pretty standard requirement but I am unable to find any guidelines.

I already extended layoutSubViews like this:

- (void) layoutSubviews
{
    [self resizeCellImage];
}

- (void) resizeCellImage
{
    CGRect descriptionRect = self.cellDescriptionLabel.frame;
    CGRect imageRect = self.cellImageView.frame;

    float descriptionBottomEdgeY = descriptionRect.origin.y +  descriptionRect.size.height;
    float imageBottomEdgeY = imageRect.origin.y + imageRect.size.height;

    if (imageBottomEdgeY >= descriptionBottomEdgeY)
        return;

    //push the bottom of image to the bottom of description
    imageBottomEdgeY = descriptionBottomEdgeY;

    float newImageHeight = imageBottomEdgeY - imageRect.origin.y;
    imageRect.size.height = newImageHeight;
    self.cellImageView.frame = imageRect;

    CGRect cellFrame = self.frame;
    cellFrame.size.height = imageRect.size.height + imageRect.origin.y + 5;

    CGRect contentFrame = self.contentView.frame;
    contentFrame.size.height = cellFrame.size.height - 1;
    self.contentView.frame = contentFrame;

    self.frame = cellFrame;
}

It pretty much tells that if description is taller than image, we must resize the image as well as cell height to fit the description.

However when I invoke this code by doing this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    cell.cellDescriptionLabel.text = @"Some long string";
    [cell.cellDescriptionLabel sizeToFit];
    [cell setNeedsLayout];

    return cell;    
}

It appears that while cell frame is changed due to layoutSubViews call, other cells do not respect it. That is, they appear on the same position had the previous cell would not have resized itself.

Two questions:

  • How to make it possible what I want?
  • Am I doing right by calling setNeedsLayout within cellForRowAtIndexPath?

P.S.: I know heightForRowAtIndexPath holds key to changing the cell height, but I feel that the data parsing (not shown here) that I do as part of cellForRowAtIndexPath would be an overkill just to calculate height. I need something that can directly tell the UITableViewCell to resize itself according to content needs.

share|improve this question
    
You could do that data parsing in heightForRowAtIndexPath and then re-use it in cellForRowAtIndexPath. –  nemesis Jan 28 '14 at 19:11
    
Good thought, however there should be a way to tell the cell itself, that here is the content for you and you resize yourself, which is what happens within cellForRowAtIndexPath - the right way to read into the cell. I know there are ways but I simply need to discover it. –  Nirav Bhatt Jan 28 '14 at 19:14
1  
I wish I could help you but I don't really have a clue on this one. But I would love to see the answer when you solve it, so, good luck! :) –  nemesis Jan 28 '14 at 19:18

3 Answers 3

up vote 3 down vote accepted

-tableView:heightForRowAtIndexPath: is by design how variable sized cells are calculated. The actual frame of a cell is of no importance and is changed by the table view to fit its needs.

You are sort of thinking of this backwards. The delegate tells the table view how cells need to be drawn, then the table view forces cells to fit those characteristics. The only thing you need to provide to the cell is the data it needs to hold.

This is because a table view calculates all the heights of all the cells before it has any cells to draw. This is done to allow a table view to size it's scroll view correctly. It allows for properly sized scroll bars and smooth quick-pans through the table view. Cells are only requested when a table view thinks a cell needs to be displayed to the screen.


UPDATE: How Do I Get Cell Heights

I've had to do this a couple of times. I have my view controller keep a cell which is never used in the table view.

@property (nonatomic) MyTableViewCell *standInCell;

I then use this cell as a stand in when I need measurements. I determine the base height of the cell without the variable sized views.

@property (nonatomic) CGFloat standInCellBaseHeight;

Then in -tableView:heightForRowAtIndexPath:, I get the height for all my variable sized views with the actual data for that index path. I add the variable sized heights to my stand in cell base height. I return that new calculated height.

Note, this is all non-autolayout. I'm sure the approach would be similar, but not identical to this, but I have no experience.

share|improve this answer
    
True, but then how to access a cell within tableView:heightForRowAtIndexPath except calling cellForRowAtIndexPath? And if I do, I would be making additional call to cellForRowAtIndexPath which is once already called by reloadData. Plus my height is content-dependent so basically I will be redoing stuff in both functions. –  Nirav Bhatt Jan 29 '14 at 5:37
    
@NiravBhatt I updated my answer. –  Jeffery Thomas Jan 29 '14 at 13:33

-tableView:heightForRowAtIndexPath: is the preferred way to tell tableview the size of its cells. You may either precalculate and cache it in a dictionary and reuse, or alternatively in ios7, you can use -tableView:estimatedHeightForRowAtIndexPath: to estimate the sizes.

share|improve this answer

Take a look at this thread - https://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights, the answer points to a very good example project here - https://github.com/caoimghgin/TableViewCellWithAutoLayout.

Sorry, but as far as I know you have to implement tableView:heightForRowAtIndexPath:. Warning, in iOS 6 this gets called on every row in you UITableView right away, I think to draw the scrollbar. iOS7 introduces tableView:estimatedHeightForRowAtIndexPath: which if implemented allows you to just guess at the height before doing all the calculation. This can help out a lot on very large tables.

What I found works well is just have your tableView:heightForRowAtIndexPath: call cellForRowAtIndexPath: to get the cell for that row, and then query that cell for it's height cell.bounds.size.height and return that.

This works pretty well for small tables or in iOS7 with tableView:estimatedHeightForRowAtIndexPath implemented.

share|improve this answer
    
Wouldn't that cause double call to cellForRowAtIndexPath? And where to put the data reading that I do as part of cellForRowAtIndexPath right now? –  Nirav Bhatt Jan 29 '14 at 5:57
    
Yeah, that's kind of my point. You are either need to calculate your height independently of the cell, or just use the cell. If you have a crazy cell using autolayout and would be tough for you to know the height, but only 10 of them, then I would say the double hit is worth it. But if you have 1000+ cells, then it might make sense to do the work to figure out the height without rendering the cell. You can also look into caching too. I found it depends on your situation. I don't like how Apple split the two up, unlike WPF that would get the height from the container. –  ansible Jan 29 '14 at 6:28

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.