My question essentially boils down to the best way to support dynamic heights of UILabel's (and I suppose other elements) in a UITableCell, and also correctly resize the label width/height and cell heights when rotating.
I'm aware of how to get the expected height of UILabels, and size the heights accordingly, but things seem to get pretty tricky when you support landscape orientation as well.
I'm thinking this might go the route of layoutSubviews, but I'm not sure how you would combine that with the table needing to calculate the height of cells. Another interesting post sets the frame manually on init to make sure they are doing calculations off a known quantity, but that only addresses part of the issue.
So here's an image of what I'm dealing with, with red arrows pointing to the dynamic height labels, and blue arrows pointing towards the cells that will change height.
I've managed to get it working correctly, but not sure if its the correct method.
cellForRowAtIndexPath
always gives its size in portrait mode. (ie. for the iphone app, it always reports 320).cellForRowAtIndexPath
gives its size in the correct orientation[tableView reloadData]
on rotation. I couldn't find any other way to update the cell and label heightsOn viewDidLoad
I grab a reference to each label font, and a float for the label width percentage compared to the tableView in portrait
CWProductDetailDescriptionCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
self.descriptionLabelWidthPercentage = cell.descriptionLabel.frame.size.width / 320;
self.descriptionLabelFont = cell.descriptionLabel.font;
In heightForRowAtIndexPath
I calculate the cell height using the tableView width and the percentage I already grabbed:
case TableSectionDescription:
CGFloat labelWidth = self.tableView.frame.size.width * self.descriptionLabelWidthPercentage;
CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(labelWidth, MAXFLOAT) AndFont:self.descriptionLabelFont];
return newLabelFrameSize.height + kTextCellPadding;
In cellForRowAtIndexPath
I calculate the frame for the label frame and update it
cell = [tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
((CWProductDetailDescriptionCell *)cell).descriptionLabel.text = self.product.descriptionText;
CGRect oldFrame = ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame;
CGFloat labelWidth = self.tableView.frame.size.width * self.descriptionLabelWidthPercentage;
CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(labelWidth, MAXFLOAT) AndFont:((CWProductDetailDescriptionCell *)cell).descriptionLabel.font];
((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, labelWidth, newLabelFrameSize.height);
In didRotateFromInterfaceOrientation
you need to [self.tableView reloadData]
cellForRowAtIndexPath
(not sure if this is exactly true)
So. What is the right way to do this? I've seen lots of SO questions, but none quite address this exactly.
Apparently it took writing out my giant question and taking a break from the computer to figure it out.
heightForRowAtIndexPath
cellForRowAtIndexPath
aside from initial setup
willDisplayCell forRowAtIndexPath
Note that these are retained/strong(ARC), and never added to the table itself
self.descriptionReferenceCell = [self.tableView dequeueReusableCellWithIdentifier:@"DescriptionCell"];
self.attributeReferenceCell = [self.tableView dequeueReusableCellWithIdentifier:@"AttributeCell"];
heightForRowAtIndexPath
do calculation for variable height cellsI first update the width of my reference cell so that it lays out its subviews (UILabels), which I can then use for calculations. Then I use the updated label width to determine the height of my labels, and add any necessary cell padding to calculate my cell height. (Remember these calculations are only necessary if your cell changes heights along with your label).
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
float width = UIDeviceOrientationIsPortrait(orientation) ? 320 : 480;
case TableSectionDescription:
CGRect oldFrame = self.descriptionReferenceCell.frame;
self.descriptionReferenceCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, width, oldFrame.size.height);
CGFloat referenceWidth = self.descriptionReferenceCell.descriptionLabel.frame.size.width;
CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(referenceWidth, MAXFLOAT) AndFont:self.descriptionReferenceCell.descriptionLabel.font];
return newLabelFrameSize.height + kTextCellPadding;
cellForRowAtIndexPath
Don't do any dynamic layout related updates relying on orientationwillDisplayCell forRowAtIndexPath
Update the label heights themselvesSince the table cells have performed layoutSubviews
, the labels already have the correct widths, which I use to calculate the exact height of the labels. I can safely update my label heights.
case TableSectionDescription:
CGRect oldFrame = ((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame;
CGSize newLabelFrameSize = [self sizeForString:self.product.descriptionText WithConstraint:CGSizeMake(oldFrame.size.width, MAXFLOAT) AndFont:((CWProductDetailDescriptionCell *)cell).descriptionLabel.font];
((CWProductDetailDescriptionCell *)cell).descriptionLabel.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, newLabelFrameSize.height);
break;
Your visible cells won't update their heights and labels if you don't reload data.
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[self.tableView reloadData];
}
The only other thing to point out is that I'm using a helper class for calculating the label height that just wraps the basic call. Really its not saving much of any code anymore (I used to have other stuff in there), so just use the normal method straight off your string.
- (CGSize) sizeForString:(NSString*)string WithConstraint:(CGSize)constraint AndFont:(UIFont *)font {
CGSize labelSize = [string sizeWithFont:font
constrainedToSize:constraint
lineBreakMode:UILineBreakModeWordWrap];
return labelSize;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With