4

I am trying to create a UITableView whose cell's heights are sized according to the amount of text being displayed. This seemed simple enough, as heightForRowAtIndexPath is available and I can get the required size needed for the UILabel using the code shown below. However, this method is called before cellForRowAtIndexPath so I cannot size based on the content in the cell, which seems a very strange choice to me. What is the "best practice" way to dynamically size the height of the cells based on content?

In addition, why did Apple not have the heightForRowAtIndexPath method be called after cellForRowAtIndexPath?

I am using the following in cellForRowAtIndexPath to determine the required height for my label, but I'm not sure how to move this to heightForRowAtIndexPath since dequeReusableCellWithIdentifier cannot be called in the method (as far as I know):

func getRequiredLabelHeight(label: UILabel, text: String, font: UIFont) -> CGFloat {
    label.numberOfLines = 0
    label.lineBreakMode = .ByWordWrapping
    label.font = font
    label.text = text
    label.sizeToFit()

    return label.frame.height

}

I also know that I want the following to be my cell height, since I want the cell size to increase from the initial size (hinging on a 1 line label) based on the new label size (which can be any arbitrary length of lines)

self.cellHeight = getRequiredLabelHeight(commentCell.commentTextLabel, text: allComments![indexPath.row].text, font: UIFont(name: "Avenir", size: 16.0)!) + commentCell.bounds.height - commentCell.commentTextLabel.bounds.height

Thanks!

EDIT My question does NOT pertain to resizing the label. The code I have provided does that. The issue is that the cell height is not constant; it varies throughout the table based on the content, so I can't use a static tableview.rowHeight. Since I cannot resize the cell height from cellForRowAtIndexPath, my content will not fit in the cell. For example, here is my cellForRowAtIndexPath (in a nutshell):

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let commentCell: CommentTableViewCell = tableView.dequeueReusableCellWithIdentifier("commentTableViewCell", forIndexPath: indexPath)
        as! CommentTableViewCell

    commentCell.commentTextLabel.textColor = Utils.hatchliTextDark
    commentCell.commentTextLabel.font = UIFont(name: "Avenir", size: 16)

    self.cellHeight = getRequiredLabelHeight(commentCell.commentTextLabel, text: allComments![indexPath.row].text, font: UIFont(name: "Avenir", size: 16.0)!) + commentCell.bounds.height - commentCell.commentTextLabel.bounds.height

    commentCell.commentTextLabel.frame = CGRectMake(commentCell.commentTextLabel.frame.origin.x, commentCell.commentTextLabel.frame.origin.y, commentCell.commentTextLabel.frame.width, self.cellHeight)
    commentCell.handleTextLabel.text = String(allComments![indexPath.row].creatorHandle)
    commentCell.votesTextLabel.text = String(allComments![indexPath.row].votes)
    commentCell.commentTextLabel.text = allComments![indexPath.row].text
    commentCell.setMargin()

    return commentCell

}

So, if the frame of the UILabel becomes larger than tableview.rowHeight, it will not appear. Thus, the row height needs to be resized on a per cell basis from heightForRowAtIndexPath (I assume)

treyhakanson
  • 4,611
  • 2
  • 16
  • 33
  • `heightForRowAtIndexPath` is called completely independent of `cellForRowAtIndexPath`. The table view needs to know its full height long before it needs to display the few cells that happen to be visible at the moment. – rmaddy Apr 06 '16 at 18:34
  • 1
    Are you using autolayout that re-sizes the cell automatically? If not, what is the current code in your `tableView:heightForRowAtIndexPath:` method? Have you looked at answers like the following: http://stackoverflow.com/q/20068849/558933 , http://stackoverflow.com/q/25180443/558933 , http://stackoverflow.com/q/27374612/558933 – Robotic Cat Apr 06 '16 at 18:35
  • can autolayout resize cell height for a UITableView on a per cell basis? – treyhakanson Apr 06 '16 at 18:56
  • See this post: http://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights – koen Apr 07 '16 at 14:44

4 Answers4

2

SOLUTION

The solution I found is a little gnarly, but I don't see any other way. Basically, I know what proportion of the screen my UILabel's width will take up, in addition to having the String of text to be displayed and the font properties. Based on these factors, I can use the following code to determine what height of UILabel is needed, and by factoring in the default cell and label height can find my required cell height before the cell itself is generated:

// width of string as a particular font in one line
func getStringSizeForFont(font: UIFont, myText: String) -> CGSize {
    let fontAttributes = [NSFontAttributeName: font]
    let size = (myText as NSString).sizeWithAttributes(fontAttributes)

    return size

}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    // get the string size
    let stringSizeAsText: CGSize = getStringSizeForFont(UIFont(name: "Avenir", size: 16.0)!, myText: allComments![indexPath.row].text)

    // label width and height are hardcoded here, but can easily be made dynamic based on the screen width
    // and the constant proportion of the width the UILabel will take up
    // example:
    // let labelWidth: CGFloat = UIScreen.mainScreen().bounds.width * widthProportion
    // originalLabelHeight can be hardcoded or made dynamic; I'm going to leave my hardcoded because I'll be leaving the height
    // the same across all devices

    let labelWidth: CGFloat = 259.0
    let originalLabelHeight: CGFloat = 20.5

    // the label can only hold its width worth of text, so we can get the ammount of lines from a specific string this way
    let labelLines: CGFloat = CGFloat(ceil(Float(stringSizeAsText.width/labelWidth)))

    // each line will approximately take up the original label's height
    let height =  tableView.rowHeight - originalLabelHeight + CGFloat(labelLines*stringSizeAsText.height)

    return height

}
treyhakanson
  • 4,611
  • 2
  • 16
  • 33
  • 2
    stringSize.width/labelWidth does not return the exact number of lines. If your line breaking mode is .ByWordWrapping, your label makes a white space at the end of line. However, the calculated stringSize does not consider these white spaces. When the sum of these white spaces' width is bigger than labelWidth, then the calculated number of lines should be wrong. – Mark Mar 03 '17 at 05:45
  • You are correct, and this is an issue I was running into occasionally. For my purposes, it didn't cause much trouble but it's definitely something anyone looking to implement this solution should consider – treyhakanson Mar 19 '17 at 19:00
1

1) The best option in my opinion for your this case would be:

override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension }

… and in “heightForRowAtIndexPath” indicate “return UITableViewAutomaticDimension”

2) If you calculate the height another option would be:

In “cellForRowAtIndexPath” you should be able to change the value for “self.tableView.rowHeight”. This option is also useful if you have different nibs for the TableView.

Marcos
  • 461
  • 6
  • 10
  • This only works if the cell is fully constrained; I was programmatically building the cells and could not set constraints in my case due to the variations in the cells. In hindsight, it would have been best to load the cells from a bunch of different fully-constrained nibs rather than trying to make one flexible cell programmatically to be used in different situations. – treyhakanson Mar 19 '17 at 19:05
0

Best option for resizing the Table view Cells height dynamically is using Autolayouts .

Set bottom of your label with bottom of that white UIView and make sure that white UIView has left,right,top and bottom constraints :)

Same type of example is explained here programmatically....

Manish Methani
  • 245
  • 2
  • 7
-2

Well I went through this tutorial and a single line of code helped me to resize the content of uitableview cell with the content of a label in it.

//=====
https://www.raywenderlich.com/129059/self-sizing-table-view-cells
//=====

Add

tableView.rowHeight = UITableViewAutomaticDimension

tableView.estimatedRowHeight = 140

in viewDidLoad() and make sure number of lines of label/textview is 0

  • 1
    This only works when the cell's views are fully constrained, I was dynamically generating the cells so this does not work for my scenario. You are correct that this will work when doing something like loading a fully constrained cell from a nib – treyhakanson Jul 06 '17 at 14:49