I have a UITableView that has five static cells. I need the cell height of one cell to adjust automatically to its contents, which is one UILabel.
Is there any way I
NOTE: This is based on @CamelBase 's answer, which is written for Objective-C, so mostly just a conversion to Swift for those that made the switch and still struggle converting stuff themselves.
For those wanting to do this using Swift that want a table with static cells, here's a working solution.
I didn't need to use this code (tried putting it in viewDidLoad()
and tried without, made no difference):
tableView.estimatedRowHeight = 42.0
tableView.rowHeight = UITableViewAutomaticDimension
First you want to add UILabel
to each cell you want auto-grow content in.
You'll need to add 4 constraints to the UILabel all relative to the parent (Content View of the cell).
This will make the label grow when the cell grows (which our code below does).
Now just add this code:
override func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
let sectionICareAbout = 2
let rowICareAbout = 1
// repeat for as many section/row combos you need dynamic sizing on
if indexPath.section == sectionICareAbout
&& indexPath == rowICareAbout {
// replace with how you get the content for the cell
// in section 2 row 1
let text = "line1\nline2\nline3"
return rowHeightForText(text)
}
// use auto-dimension if we didn't set something else
return UITableViewAutomaticDimension
}
func rowHeightForText(text: String) -> CGFloat {
let labelWidth = tableView.bounds.size.width
let attributedText = NSAttributedString(string: text)
let options = NSStringDrawingOptions.UsesLineFragmentOrigin
let size = CGSize(width: labelWidth, height: CGFloat.max)
let boundingRect = attributedText.boundingRectWithSize(size,
options: options, context: nil)
return boundingRect.size.height
}
I assume that I would need to tell the cell to redraw using something like
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:@[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
When it comes to dynamically changing the height of the tableviewcells.
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return Yourvalue; //return whatever u want ur cell to have size.
}
Expanding over Victors' answer, Static Cells based table views seem to autosize cells based on content and constraints once the following delegates are implemented:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
EXCEPT for when there are labels. For some reason the label intrinsic content heights do not seem to contribute towards the height calculation of the cell when they have . My current work around for this is to nest the labels in an UIView.
Do the following:
Certainly feels like a hack but works for me in all the cases I have tried.
Ended up doing this. Not what I wanted, but it kind of works for my simple requirements and text lengths.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 2)
{
CGFloat padding = 30.0f;
CGFloat labelWidth = self.tableView.bounds.size.width - padding*2;
NSAttributedString *text = [[NSAttributedString alloc] initWithString:_freeTextLabel.text];
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin;
CGRect boundingRect = [text boundingRectWithSize:CGSizeMake(labelWidth, CGFLOAT_MAX)
options:options
context:nil];
return (CGFloat) (ceil(boundingRect.size.height) + padding*2);
}
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
Here is a simple example with Swift 3. We have a UITableViewController
in Storyboard set with type grouped and content of type static cells. Its tableView
contains one UITableViewCell
. The cell contains one UILabel
that has a number of lines set to 0 and some very long text to display. The label also has four Auto layout constraints with its superView
(top, bottom, leading and trailing).
Now, you may choose one of the four following code snippets in order to allow your static cell to automatically resize itself according to its content.
tableView(_:estimatedHeightForRowAt:)
and tableView(_:heightForRowAt:)
Note: tableView(_:estimatedHeightForRowAt:)
method requires iOS7
import UIKit
class TableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100 // Return `UITableViewAutomaticDimension` if you have no estimate
}
}
estimatedRowHeight
property and tableView(_:heightForRowAt:)
Note: estimatedRowHeight
property requires iOS7
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
}
tableView(_:heightForRowAt:)
import UIKit
class TableViewController: UITableViewController {
// Bind this IBOutlet to the cell in Storyboard
@IBOutlet weak var cell: UITableViewCell!
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let fittingSize = CGSize(width: tableView.bounds.size.width, height: 0)
let systemLayoutSize = cell.systemLayoutSizeFitting(fittingSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel)
return systemLayoutSize.height
}
}
UITableViewCell
subclass and tableView(_:heightForRowAt:)
import UIKit
class TableViewController: UITableViewController {
// 1. Set custom class to `CustomCell` for cell in Storyboard
// 2. Bind this IBOutlet to the cell in Storyboard
@IBOutlet weak var cell: CustomCell!
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
cell.layoutIfNeeded()
let contentViewSize = cell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return contentViewSize.height + 1 // + 1 for the separator
}
}
class CustomCell: UITableViewCell {
// 1. Set custom class to `CustomCell` for cell in Storyboard
// 2. Bind this IBOutlet to the cell's label in Storyboard
@IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
label.preferredMaxLayoutWidth = frame.width
}
}
All four previous code snippets will result in the following display:
In IOS8, I've found that explicitly setting the row height/width or label height/width in code causes more problems than it fixes. I have created dynamic and static tables with varying cell height and multiline labels using autolayout, but you really have to follow Apple standard to the letter or weird things happen (like separators disappearing or rows randomly collapsing in height).
[edited] Set estimatedHeightForRowAtIndexPath and heightForRowAtIndexPath to automatic dimensions in your UITableViewDelegate
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Make sure every single element in the cell has a top/bottom/left/right = constraint (not >=, <=, or alignX or alignY - it has to be =). You can make this a low priority constraint and provide a better constraint of higher priority, but you have to give it an exact starting value to size the cell.