I have a chat function in the app I am making. Each cell has a UITextView and I need to calculate the **height and width** of each cell. I do that and then cache that data.
Using NSLog I have determined that the data is correctly cached and the height for each row is only being asked for once.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *message = [feedItems objectAtIndex:indexPath.row];
NSString *reuseIdentifier;
if (message.iSentThisMessage) {
reuseIdentifier = #"MeCell";
} else {
reuseIdentifier = #"HostCell";
}
ChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textView.text = message.content;
if (message.frame.size.height == 0 && message.frame.size.width == 0) {
CGRect rect = cell.textView.frame;
rect.origin.x = 5;
rect.origin.y = 3;
CGSize size = [self text:message.content
sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(225.0, CGFLOAT_MAX)];
rect.size.height = size.height
+ cell.textView.textContainerInset.bottom
+ cell.textView.textContainerInset.top;
rect.size.width = [cell.textView sizeThatFits:CGSizeMake(225.0, rect.size.height)].width
+ cell.textView.textContainerInset.left
+ cell.textView.textContainerInset.right;
if (message.iSentThisMessage) {
rect.origin.x = 320 - (rect.origin.y + rect.size.width);
}
cell.textView.frame = rect;
message.frame = rect;
NSLog(#"Not Cached :(");
} else {
cell.textView.frame = message.frame;
NSLog(#"Cached!");
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *message = [feedItems objectAtIndex:indexPath.row];
CGSize size = [self text:message.content
sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(225.0, CGFLOAT_MAX)];
if (indexPath.row + 1 == feedItems.count)
size.height += 4;
NSLog(#"Height asked for.");
return size.height + 12;
}
Also, one other possibility may be that I am overriding the following method in my custom uitableviewcell class because otherwise it rearranges the contents of my cell.
- (void) layoutSublayersOfLayer:(CALayer *)layer {
}
I can't seem to figure out how to make it not lag!!
Not clear why you're using a text view, but you should change to a label if you can.
Also, don't cache the height in your table delegate method, that's a nasty side effect. Calculate the height up front, either when the message is loaded / received, or on a background thread for batch load.
The text view / label frame should also be relative to the cell, either by auto resizing / layout. So you should never set it explicitly.
On top of that - profile using instruments. This is the only thing that will really tell you what's wrong.
Related
I am creating an application with iOS 7 devices. This application contains UITableView with dynamic cell height.
To create this I have a custom cell which contains methods:
- (CGFloat)heightWithModel:(Model *)model
{
CGFloat height = 0.0f;
height = self.contentLabel.frame.origin.y + [self contentHeightWithModel:model] + CellBottomOffset;
return height;
}
and...
- (CGFloat)contentHeightWithModel:(Model *)model
{
CGFloat height = 0.0;
NSDictionary *attributes = [NSDictionary dictionaryWithObject:[UIFont systemFontOfSize:14.0f] forKey:NSFontAttributeName];
NSString *string = model.content;
NSStringDrawingContext *context = nil;
NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin;
CGSize size = CGSizeMake(self.contentLabel.bounds.size.width, CGFLOAT_MAX);
CGRect frame = [string boundingRectWithSize:size options:options attributes:attributes context:context];
height = frame.size.height;
return height;
}
In my view controller, I have implemented UITableViewDelegate protocol method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat height;
static Cell *cell;
if (!cell)
{
cell = [tableView dequeueReusableCellWithIdentifier:CellReuseIdentifier];
}
height = [cell heightWithModel:[self.dataSource.models objectAtIndex:indexPath.row]];
return height;
}
As far as I understand, this should be enough to create a table view with dynamic cell height. Despite this, I have a table view like this:
As you see, part of text are hidden, because label (red one) height is too small. Cell height is set dynamically by using Auto Layout (10 px from the bottom).
Can anyone see where is the problem?
First take a variable
CGFloat height;
put this lines in viewdidload method
in that set estimated height of cell and for that label set lines to 0 in inspector panel and change its height = to >= and give constraint from all side
[self.tbl_rating layoutIfNeeded];
[self.tbl_rating setNeedsLayout];
if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1 )
{
self.tbl_rating.estimatedRowHeight=153.0f;
self.tbl_rating.rowHeight=UITableViewAutomaticDimension;
}
Now put this two method for finding label height and dynamic cell height increment
In this method set your label text that you retriving from like array or web service
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// 7.1>
if (NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1 ) {
height=[self findHeightForText:[NSString stringWithFormat:#"%#",[[arr_review valueForKey:#"desc"]objectAtIndex:indexPath.row]] havingWidth:self.view.frame.size.width andFont:[UIFont systemFontOfSize:12.0f]].height;
return 153+height;
}
return UITableViewAutomaticDimension;
}
-(CGSize)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font {
CGSize size = CGSizeZero;
if (text) {
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{ NSFontAttributeName:font } context:nil];
size = CGSizeMake(frame.size.width, ceil(frame.size.height));
}
return size;
}
NOTE: In tableview method heightForRowAtIndexPath I recommand you pass text from array value. If this thing doesnt work then check your constraints.
This thing works for me Hope it will work.
Thank you.
The Problem:
I've got a label in a table cell as follows:
EDIT: While the tableview does not use AutoLayout, the xib for the cell does. This is what it looks like:
Now, the "..." means the review (the text) is too long to be displayed. The user can then click on the cell, which would expand the cell and the label downwards, which would then show the rest of the text.
Here's the code for the heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
if (tableView == self.rateAndReviewView.ratingsTable) {
BOOL isSelected = [self.selectedIndexPaths containsObject:indexPath];
CGFloat maxHeight = MAXFLOAT;
CGFloat minHeight = 40.0f;
if (isSelected){
CGFloat constrainHeight = isSelected?maxHeight:minHeight;
CGFloat constrainWidth = tableView.frame.size.width - 20.0f;
//
SQLReview *review = [reviewsArray objectAtIndex:indexPath.row];
NSString *text = review.comment;
CGSize constrainSize = CGSizeMake(constrainWidth, constrainHeight);
CGSize labelSize = [text sizeWithFont:[UIFont systemFontOfSize:15.0f]
constrainedToSize:constrainSize
lineBreakMode:NSLineBreakByWordWrapping];
CGFloat labelHeight = labelSize.height;
return MAX(labelHeight+20, 100.0f);
} else {
minHeight = 100.0f;
return minHeight;
}
}
}
And this is the code for the cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == self.rateAndReviewView.ratingsTable) {
static NSString *CellIdentifier = #"Cell";
VenueReviewCell *cell = (VenueReviewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell==nil){
cell = [[[NSBundle mainBundle] loadNibNamed:#"ReviewCell" owner:nil options:nil] objectAtIndex:0];
}
[cell.contentView.layer setBorderWidth:0.5f];
cell.contentView.backgroundColor = [UIColor whiteColor];
SQLReview *review = [reviewsArray objectAtIndex:indexPath.row];
cell.usernameLabel.text = (review.user_name == nil) ? #"" : review.user_name;
cell.datetimeLabel.text = (review.datetime == nil) ? #"" : [review.datetime substringToIndex:10];
cell.commentLabel.text = (review.comment == nil) ? #"" : review.comment;
cell.commentLabel2.text = (review.comment == nil) ? #"" : review.comment;
float overallScore = (review.overall == nil) ? 0.0f : [review.overall floatValue];
NSArray *overallImageViewArray = #[cell.overallStarImageView1, cell.overallStarImageView2, cell.overallStarImageView3, cell.overallStarImageView4, cell.overallStarImageView5];
if([selectedIndexPaths containsObject:indexPath]) {
cell.commentLabel.hidden = YES;
cell.commentLabel2.hidden = NO;
CGSize commentLabelComputeSize = [cell.commentLabel2.text sizeWithFont:cell.commentLabel2.font constrainedToSize:CGSizeMake(cell.commentLabel2.frame.size.width, CGFLOAT_MAX) lineBreakMode:cell.commentLabel2.lineBreakMode];
CGFloat commentLabelHeightOffset = (commentLabelComputeSize.height - cell.commentLabel2.frame.size.height > 0) ? (commentLabelComputeSize.height - cell.commentLabel2.frame.size.height) : 0;
cell.commentLabel2.frame = CGRectMake(cell.commentLabel2.frame.origin.x, cell.commentLabel2.frame.origin.y, cell.commentLabel2.frame.size.width, cell.commentLabel2.frame.size.height + commentLabelHeightOffset);
cell.commentLabel2.autoresizingMask = UIViewAutoresizingFlexibleHeight;
cell.commentLabel2.numberOfLines = 0;
} else {
cell.commentLabel.hidden = NO;
cell.commentLabel2.hidden = YES;
}
[CommonUtilities displayStarRatingsWithScore:overallScore starImageViewArray:overallImageViewArray];
return cell;
}
return nil;
}
The isSelected part from heightForRowAtIndexPath does work, and the table cell does visibly expand on tap, but the text remains the same - at least, as tested on iOS 7+ devices.
EDIT: I also logged out the values of cell.commentLabel2.frame, by adding this:
NSLog(#"VenueTabViewController: cellForRow: contains indexpath %#, size: %#", indexPath, NSStringFromCGRect(cell.commentLabel2.frame));
in both the if and else of cellForRowAtIndexPath, and it shows the following:
On first tap:
VenueTabViewController: cellForRow: contains indexpath {length = 2, path = 0 - 1}, size: {{20, 31}, {285, 60.579999999999998}}
On second tap:
VenueTabViewController: cellForRow: contains indexpath {length = 2, path = 0 - 2}, size: {{20, 31}, {285, 21}}
So it does change in size.
What I tried:
I googled this, and found out that [string sizeWithFont:[UIFont systemFontOfSize:15.0f] constrainedToSize:constrainSize lineBreakMode:NSLineBreakByWordWrapping]; has been depreciated. So instead, I replaced the lines relating to it above with:
CGSize labelSize = [text sizeWithAttributes:#{NSFontAttributeName: [UIFont systemFontOfSize:17.0f]}];
CGFloat labelHeight = ceilf(labelSize.height);
Which not only failed to work, it also stopped the cell from expanding, which I thought might be a step backwards.
I also tried adding the following, because why not:
VenueReviewCell *cell = (VenueReviewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell==nil){
cell = [[[NSBundle mainBundle] loadNibNamed:#"ReviewCell" owner:nil options:nil] objectAtIndex:0];
}
[cell.commentLabel sizeToFit];
But it's still a nope.
Then, I simply replaced commentLabelHeightOffset with 100, just to test if anything will change:
cell.commentLabel2.frame = CGRectMake(cell.commentLabel2.frame.origin.x, cell.commentLabel2.frame.origin.y, cell.commentLabel2.frame.size.width, cell.commentLabel2.frame.size.height + commentLabelHeightOffset);
Nothing happened.
EDIT: Here's what I did that finally gained traction - I removed the AutoLayout on the cells, as per the suggestion of saif. And now, while the cells do expand, and the labels along with it, for some reason I get this:
I checked the code, and the X and Y of the label frame aren't changed (just the frame height) and the text has no "new lines" before the actual text.
Can I have some help please?
Try this, Set the frame to desc label in cell for row(depending on text size), and reload the table view when you want to expand the cell.
In cellForRowAtIndexPath
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
//code to add desc label
}
CGSize suggestedSize = [descriptionText sizeWithFont:descLabel.font constrainedToSize:CGSizeMake(descLabel.frame.size.width, FLT_MAX) lineBreakMode:descLabel.lineBreakMode];
float padding = 20;
[descLabel setFrame:CGRectMake(padding, CGRectGetMaxY(nameLabel.frame), CGRectGetWidth(tableView.frame)-(2*padding), suggestedSize.height)];
if you are using auto layouts in custom cell then
Keep a back up, and uncheck auto layouts
select iPhone in drop down and select Disable size class
If there is a problem with row height,
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGSize suggestedSize = [descriptionText sizeWithFont:descLabel.font constrainedToSize:CGSizeMake(descLabel.frame.size.width, FLT_MAX) lineBreakMode:descLabel.lineBreakMode];
float heightOfRow = CGRectGetMaxY(nameLabel.frame)+suggestedSize.height+5; // height of name label(as it is single line supported) + height of desc label + bottom padding
return heightOfRow;
}
Hope this helps
Try autoresizing:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
VenueReviewCell *cell = (VenueReviewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.commentLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight;
cell.commentLabel.numberOfLines = 0;
return cell;
}
If you want to continue using autolayout:
-set the numberOfLines to '0'
-Implement the following two TableView methods
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
Working with Self-Sizing Table View Cells Here is a useful reference.
If you're using manual layout then you must set the numberOfLines property of your label to 0, also you will want to call -sizeToFit on the label after setting the text.
You might also need to change some other frames after this accordingly so the label doesn't overlap anything.
If you are using AutoLayout then setting the numberOfLines is still needed but then it depends on your constraints, if you've set the left and right of the label to the super view's left and right then it can infer a maximum width and grow the label vertically.
In any case your screenshot clearly shows that the label is set to have numberOfLines = 1, your cell height looks okay, which is expected since you're calculating that in code with a set width. It's the rendering of the label which is not being triggered. Change numberOfLines to 0 to start with, then in layoutSubviews (if you're doing things manually) you'll want to sizeToFit
I am working in storyboard. I have View Controller inherited from tableViewVC. In that vc I have cells with one or 2 lines of text. If I have one line, I just assign text to a custom textLabel in custom Cell. If I have 2 lines, I resize the labels frame. But it does not work when the table is loaded. I can see my resized labels in cell only after I scroll tableView up/down. How can I fix it?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
OptionItem *item = _currentOptionsList[indexPath.row];
static NSString *CellIdentifier = #"OptionSimpleCellIdentifier";
OptionSimpleCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.titleLabel.text = item.title;
cell.haveDetail = item.haveDetailItems;
cell.selectedSimpleOption = item.simpleSelected;
if (item.haveSelectedDetailItem)
{
cell.detailLabel.text = [item.detailItems[item.indexOfSelecteDetaildItem] title];
} else {
cell.detailLabel.text = nil;
cell.detailImageView.image = nil;
}
CGFloat textHeight = [Calculatons getLabelHeightForText:item.title andWidth:225 withFont:kFontListCell withMaxHeight:0];//19 - 39
CGRect rect = cell.titleLabel.frame;
NSLog(#"textHeight = %f cell.height = %f titleLabel.height %f \n",textHeight, cell.frame.size.height, cell.titleLabel.frame.size.height);
if (textHeight > 21)
{
rect.size.height = 41;
cell.titleLabel.frame = rect;
}
//nothing of this not works
[cell.titleLabel layoutIfNeeded];
[cell.titleLabel setNeedsLayout];
[cell.titleLabel setNeedsUpdateConstraints];
[cell.titleLabel setNumberOfLines:0];
return cell;
}
http://s23.postimg.org/8tszefzbf/Screen_Shot_2014_08_27_at_10_01_10_PM.png
After the first screen, i push the VS. second screen is what is shown after i scroll the table up and down.
I need second screen as a result after i push VC.
try using this earlier in the code, right after the cell is initialised instead of after the value is set to the label.
CGFloat textHeight = [Calculatons getLabelHeightForText:item.title andWidth:225 withFont:kFontListCell withMaxHeight:0];//19 - 39
CGRect rect = cell.titleLabel.frame;
NSLog(#"textHeight = %f cell.height = %f titleLabel.height %f \n",textHeight, cell.frame.size.height, cell.titleLabel.frame.size.height);
if (textHeight > 21)
{
rect.size.height = 41;
cell.titleLabel.frame = rect;
}
I have a static table view that I am using as a sot of data entry for my app. One of those cells has a UITextView in it, which needs to grow and shrink dynamically as the user types, as well as the cell growing/shrinking logically with it. I read a bunch of posts on this but I think I am getting thrown because I am using a STATIC table view.
Here is my resizing attempt (its pretty much a total fail):
- (void)textViewDidChange:(UITextView *)textView
{
self.numberOfLines = (textView.contentSize.height / textView.font.lineHeight) - 1;
float height = 44.0;
height += (textView.font.lineHeight * (self.numberOfLines - 1));
CGRect textViewFrame = [textView frame];
textViewFrame.size.height = height - 10.0; //The 10 value is to retrieve the same height padding I inputed earlier when I initialized the UITextView
[textView setFrame:textViewFrame];
[self.tableView beginUpdates];
[self.tableView endUpdates];
[self.messageTextView setContentInset:UIEdgeInsetsZero];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
float height = 44.0;
if (self.messageTextView) {
height += (self.messageTextView.font.lineHeight * (self.numberOfLines - 1));
}
return height;
}
I'll take any tips! Thanks.
You don't have to do anything just go through this code:-
There is a new function to replace sizeWithFont, which is boundingRectWithSize.
Add the following function to my project, which makes use of the new function on iOS7 and the old one on iOS lower than 7. It has basically the same syntax as sizeWithFont:
-(CGSize)text:(NSString*)text sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)size{
if(IOS_NEWER_OR_EQUAL_TO_7){
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
nil];
CGRect frame = [text boundingRectWithSize:size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:attributesDictionary
context:nil];
return frame.size;
}else{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return [text sizeWithFont:font constrainedToSize:size];
#pragma clang diagnostic pop
}
}
You can add that IOS_NEWER_OR_EQUAL_TO_7 on your prefix.pch file in your project as:
#define IOS_NEWER_OR_EQUAL_TO_7 ( [ [ [ UIDevice currentDevice ] systemVersion ] floatValue ] >= 7.0 )
I have referred from this link:-
UITableViewCell with UITextView height in iOS 7?
Please check my answer.
Calculate UITableViewCell height to fit string
Here label is used to calculate the height of cell.
When you done with editing of Text in textfield - (void)textViewDidEndEditing:(UITextView *)textView call the tableview reloadData function to update the cell as per text entered in textview
Your will need to manually calculate new size of you textView, and manually call update for your tableView.
static CGFloat cellHeight = 85;//from story board height to
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if (indexPath.row == 0) {
return cellHeight + [self appendTextViewNewHeight];
}
return cell.bounds.size.height;
}
-(CGFloat) appendTextViewNewHeight {
return _appendHeightForTextView;
}
static CGFloat textViewHeightStartHeight = 33;//from storiboard height
- (void)textViewDidChange:(UITextView *)textView;
{
CGFloat fixedWidth = textView.frame.size.width;
CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
CGRect newFrame = textView.frame;
newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height);
if (newFrame.size.height >= textViewHeightStartHeight) {
CGFloat newAppend = newFrame.size.height - textViewHeightStartHeight;
if (newAppend != self.appendHeightForTextView) {
self.appendHeightForTextView = newAppend;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
}
}
Also you need to set delegates on storyboards on in the code.
in viedDidLoad you need to do next things:
self.appendHeightForTextView = 0;
And finally set constraints in storyboard for your cell. Do not set bottom constraint of your textView. Only set right, left, and top.
This question has been asked 100 times on SO, but I'm not able to solve my problem.
I've got two customizable cells. Both can have dynamic heights. My tableView:heightForRowAtIndexPath: returns the proper height for each cell, but when cells are reused, the separator line between cells isn't in the right spot 100% of the time. I can't use the hack of adding my own line in my cells because sometimes the overlap is so bad you can't click on the right cell.
What do I need to do to make sure it uses the proper height?
Here is an example of the separator line being in the wrong spots (supposed to be right above the title):
Here is how I set my heights:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
InspectionItem *item = [items objectAtIndex:indexPath.row];
int t = [item.rating.ratingType ratingTypeK];
if (t == RatingTypeTextfield){
CGFloat height = [OQCTextFieldCell heightOfCellWithTitle:item.name
subtext:item.descriptionText
text:item.comment
andScreenWidth:self.view.frame.size.width];
return height;
}
else {
CGFloat height = [OQCButtonCell heightOfCellWithTitle:item.name
subtext:item.descriptionText
andScreenWidth:self.view.frame.size.width];
return height;
}
}
Again, these class methods return the proper heights.
Here is how I set my cells up (short version):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
InspectionItem *item = [items objectAtIndex:indexPath.row];
int t = [item.rating.ratingType ratingTypeK];
if (t == RatingTypeTextfield){
static NSString *CellIdentifier = #"TextFieldCell";
OQCTextFieldCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.titleLabel.text = item.name;
...
return cell;
}
else {
static NSString *CellIdentifier = #"ButtonCell";
OQCButtonCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.titleLabel.text = item.name;
...
return cell;
}
}
Here is how I'm registering my cells:
UINib *buttonNib = [UINib nibWithNibName:#"OQCButtonCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:buttonNib forCellReuseIdentifier:#"ButtonCell"];
UINib *textFieldNib = [UINib nibWithNibName:#"OQCTextFieldCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:textFieldNib forCellReuseIdentifier:#"TextFieldCell"];
UINib *flagNib = [UINib nibWithNibName:#"OQCFlagCell" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:flagNib forCellReuseIdentifier:#"FlagCell"];
My cells only overwrite layoutSubviews and they do call super first thing.
I need the solution to be good for iOS 5.2+.
Update
Here is my code for layoutSubviews. I do believe that my custom method heightOfCellWithTitle:subtext:text: returns the right height since it matches what the layoutSubview height variable is at the end of this method.
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat height = 0;
// Title + Header background view
NSString *title = titleLabel.text;
CGRect frame = self.titleLabel.frame;
CGSize size = [title sizeWithFont:titleLabel.font
constrainedToSize:CGSizeMake(frame.size.width, 9999)
lineBreakMode:titleLabel.lineBreakMode];
size.width = frame.size.width; // don't let it shrink
titleLabel.numberOfLines = size.height / titleLabel.font.lineHeight;
frame.size = size;
self.titleLabel.frame = frame;
frame = self.headerBackgroundView.frame;
frame.size.height = size.height + 8;
self.headerBackgroundView.frame = frame;
// single line of text should be 30
height += frame.size.height;
CGRect subContentFrame = subContentView.frame;
subContentFrame.origin.y = height;
CGFloat subHeight = 0;
// Label
title = subtextLabel.text;
if ([title length] > 0){
subHeight += 5; // top margin
size = [title sizeWithFont:subtextLabel.font
constrainedToSize:CGSizeMake(subtextLabel.frame.size.width, 9999) lineBreakMode:subtextLabel.lineBreakMode];
size.width = self.contentView.frame.size.width - 52; // don't let it shrink
subtextLabel.numberOfLines = size.height / subtextLabel.font.lineHeight;
frame = self.subtextLabel.frame;
frame.size = size;
frame.origin.y = subHeight;
self.subtextLabel.frame = frame;
// subtext height + bottom margin
subHeight += size.height + 5;
}
else {
subHeight += 25;
}
// Button
frame = button.frame;
frame.origin.y = subHeight;
button.frame = frame;
subHeight += frame.size.height + 25;
subContentFrame.size.height = subHeight;
subContentView.frame = subContentFrame;
// reposition detailIndicator
frame = self.detailIndicator.frame;
frame.origin.x = subContentView.frame.size.width - 37;
self.detailIndicator.frame = frame;
height += subHeight;
// Drawer
frame = CGRectMake(0, 0, self.frame.size.width, height);
[self.drawerView setFrame:frame];
self.backgroundView = self.drawerView;
}
Also, quick note about the numbers I've been checking.
I have a log on layoutSubviews and tableView:heightForRowAtIndexPath: which give me correct heights (I'm basing that off of layoutSubviews since it is recalculating the height for the entire cell). And then I have a log for tableView:cellForRowAtIndexPath: which at the end just before I return the cell I print out the cell's height, and it is an old height of a reused view, which I kind of expect. I would assume that after the cell is returned that iOS will recalculate what the position of the cell should be based on tableView:heightForRowAtIndexPath: and reposition the views with layoutSubviews.
so if you layout the cells dynamically in the cell class and overwrite layoutSubviews this should be enough in heightForRowAtIndexPath:
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
float height = cell.frame.size.height;
return height;
remember that you have to "reset" all uiviews in your cell to the start values. otherwise the the cell will use the current values, i.e. if you have a label at y=10 and set it programmatically to y=20, next time the cell is used the cell still have y=20.
Have you tried calling
[cell setNeedsLayout];
[cell layoutIfNeeded];
before returning it?