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.

有帮助吗?

解决方案

-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.

其他提示

-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.

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.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top