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;
}
Related
I'm creating a messenger app, which displays a dialog between two people.
Parsing API response, I got text for inbox and outbox messages. Then I create a cell, using a UITableViewCell prototype from the storyboard.
All constraints are adjusted correctly.
The problem is that I use
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
to scroll the tableView to the bottom to have the last message in focus but the contentView of the upper cells is not counted at this time.
So i had to count height of cell before it is displayed. For this i use
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
RJMessageCell *cell = [self configureBasicCellAtIndexPath:indexPath];
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGSize maximumSize = CGSizeMake(320.0, UILayoutFittingCompressedSize.height);
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:maximumSize].height;
return height;
}
- (RJMessageCell *)configureBasicCellAtIndexPath:(NSIndexPath *)indexPath {
static NSString *inboxIdentifier = #"Inbox";
static NSString *outboxIdentifier = #"Outbox";
NSString *identifier;
RJMessage *message = [[[self.messageSectionsArray objectAtIndex:indexPath.section] messages] objectAtIndex:indexPath.row];
if (message.messageIsMine) {
identifier = outboxIdentifier;
} else {
identifier = inboxIdentifier;
}
RJMessageCell *cell = [self.tableView dequeueReusableCellWithIdentifier:identifier];
cell.messageView.layer.cornerRadius = 10.f;
cell.messageView.clipsToBounds = YES;
if ([message.text isEqualToString:#""]) {
cell.messageTextLabel.text = #" ";
} else {
cell.messageTextLabel.text = message.text;
}
cell.messageTextLabel.numberOfLines = 0;
cell.messageTextLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.timeLabel.text = [self stringTimeFromTimeInterval:message.messageInterval];
if (message.messageState == RJMessageStateUnread) {
cell.backgroundColor = [UIColor colorWithRed:151/255.0 green:200/255.0 blue:255/255.0 alpha:0.4];
} else {
cell.backgroundColor = [UIColor clearColor];
}
return cell;
}
Also the UILabel on screenshots are custom, to setBounds to label
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
if (self.numberOfLines == 0 && bounds.size.width != self.preferredMaxLayoutWidth) {
self.preferredMaxLayoutWidth = self.bounds.size.width;
[self setNeedsUpdateConstraints];
}
}
And the last thing i do, adding estimatedRowHeight in viewDidLoad
self.tableView.estimatedRowHeight = self.tableView.rowHeight;
self.tableView.rowHeight = UITableViewAutomaticDimension;
The first time they appear everything looks good, but when I scroll the table up and down, my cells change their size randomly.
What's wrong with the cell height?
You have to return Height by calculating the text width & height which you want to place inside the cell.
//Height For Row at index path
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellText = [NSString stringWithFormat:#"%#",[arrDataSource[indexPath.row]]];
UIFont *cellFont = [UIFont fontWithName:#"Helvetica" size:15.0];
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:cellText
attributes:#
{
NSFontAttributeName: cellFont
}];
CGRect rect = [attributedText boundingRectWithSize:CGSizeMake(tableView.bounds.size.width - 90.0, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
return rect.size.height + 42;
}
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 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.
I use auto layout and want to display UITableView with custom cell that has a UITextView with variable content.
Cells are displayed as shown below. For chat message, when cell is first displayed only 2-3 characters are displayed in a single line and next characters are forced to go to next line. But when I scroll tableView so that these cells are not visible and scroll back to make them visible (I do this so that when they are visible again cellForRowAtIndexPath: method is called again to render the cell), they render the cell properly as shown in second image.
Code :
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [chatData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RS_User *user = [[RS_User alloc]init];
chatCell *cell = (chatCell *)[tableView dequeueReusableCellWithIdentifier:CHAT_CELL_IDENTIFIER];
NSUInteger row = indexPath.row;
cell.chatCellBackground.image = nil;
NSString *chatText = [[chatData objectAtIndex:row] objectForKey:TEXT]; // get text string(message) from array
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
UIFont *font = [UIFont systemFontOfSize:FONT_SIZE];
cell.textString.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE]; // set text font
cell.textString.text = chatText;
// set text
CGSize size = [cell.textString.text sizeWithFont:cell.textString.font constrainedToSize:CGSizeMake(SET_WIDTH, MAXFLOAT) lineBreakMode:NSLineBreakByCharWrapping];
cell.textString.frame = ...;
[cell.textString sizeToFit];
NSDate *theDate = [[chatData objectAtIndex:row] objectForKey:DATE];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:DATE_FORMAT];
NSString *timeString = [formatter stringFromDate:theDate];
cell.timeLabel.text = timeString; // set timeLabel to display date and time
cell.timeLabel.font = [UIFont fontWithName:FONT_NAME size:SMALL_FONT_SIZE];
cell.userLabel.text = #"Name";//[[chatData objectAtIndex:row] objectForKey:NAME]; // set userLabel to display userName
CGSize size1 = [cell.userLabel.text sizeWithFont:font constrainedToSize:CGSizeMake(SET_WIDTH, MAXFLOAT) lineBreakMode:NSLineBreakByCharWrapping];
// check cell contains sender name or receiver name
if (thisCellIsForSender)
{
// Set bubble for sender
cell.chatCellBackground.image = [[UIImage imageNamed:#"bubbleMine.png"] stretchableImageWithLeftCapWidth:STRETCHED_WIDTH topCapHeight:STRETCHED_HEIGHT];
cell.chatCellBackground.frame = ...;
}
else
{
// set bubble for receiver
cell.chatCellBackground.image = [[UIImage imageNamed:#"bubbleSomeone.png"] stretchableImageWithLeftCapWidth:STRETCHED_WIDTH topCapHeight:STRETCHED_HEIGHT];
cell.chatCellBackground.frame = ...;
}
[cell setNeedsLayout];
[cell layoutIfNeeded];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
RS_User *user = [[RS_User alloc]init];
chatCell *cell = (chatCell *)[tableView dequeueReusableCellWithIdentifier:CHAT_CELL_IDENTIFIER];
NSUInteger row = indexPath.row;
cell.chatCellBackground.image = nil;
NSString *chatText = [[chatData objectAtIndex:row] objectForKey:TEXT]; // get text string(message) from array
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
UIFont *font = [UIFont systemFontOfSize:FONT_SIZE];
cell.textString.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE]; // set text font
cell.textString.text = chatText;
// set text
CGSize size = [cell.textString.text sizeWithFont:cell.textString.font constrainedToSize:CGSizeMake(SET_WIDTH, MAXFLOAT) lineBreakMode:NSLineBreakByCharWrapping];
cell.textString.frame = ...;
[cell.textString sizeToFit];
NSDate *theDate = [[chatData objectAtIndex:row] objectForKey:DATE];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:DATE_FORMAT];
NSString *timeString = [formatter stringFromDate:theDate];
cell.timeLabel.text = timeString; // set timeLabel to display date and time
cell.timeLabel.font = [UIFont fontWithName:FONT_NAME size:SMALL_FONT_SIZE];
cell.userLabel.text = [[chatData objectAtIndex:row] objectForKey:NAME]; // set userLabel to display userName
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
CGSize size1 = [cell.userLabel.text sizeWithFont:font constrainedToSize:CGSizeMake(SET_WIDTH, MAXFLOAT) lineBreakMode:NSLineBreakByCharWrapping];
// check cell contains sender name or receiver name
if (thisCellIsForSender)
{
// Set bubble for sender
cell.chatCellBackground.image = [[UIImage imageNamed:#"bubbleMine.png"] stretchableImageWithLeftCapWidth:STRETCHED_WIDTH topCapHeight:STRETCHED_HEIGHT];
cell.chatCellBackground.frame = ...;
}
else
{
// set bubble for receiver
cell.chatCellBackground.image = [[UIImage imageNamed:#"bubbleSomeone.png"] stretchableImageWithLeftCapWidth:STRETCHED_WIDTH topCapHeight:STRETCHED_HEIGHT];
cell.chatCellBackground.frame = ...;
}
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
return cell.bounds.size.height;
}
UPDATE : I could solve some of the issues from coverback's answer. Still cell is not laid out correctly. I am adding photos for constraints of each UI object in cell.
User name :
Time stamp :
Chat message :
Bubble image :
Cell Layout :
Cell height in cellForRowAtIndexPath: is always 100 even though I return different value from heightForRowAtIndexPath: method. Cell height in storyboard is 100.
Chat message in third cell is clipped from bottom.
Time stamps and chat message labels are some time not aligned properly to each other even though both have constraint vertical spacing from username label.
Username label in third cell is vanished.
Do you see anything wrong in constraints? You can ask me if analyzing constraints is difficult.
Combining the answers by thandasoru and coverback solved my issue. Below is the code I use.
I added below method in custom UITableViewCell's class.
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat maxTextWidth = [UIScreen mainScreen].bounds.size.width * 0.40;
self.userLabel.preferredMaxLayoutWidth = maxTextWidth;
self.chatMessage.preferredMaxLayoutWidth = maxTextWidth;
self.timeLabel.preferredMaxLayoutWidth = self.timeLabel.frame.size.width;
[super layoutSubviews];
}
UITableView datasource methods :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell * cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"customCellId"];
// Set all label's text and image to ImageView
...
// Update layout
[cell setNeedsLayout];
[cell layoutIfNeeded];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
customCell * cell = (customCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
NSString * userName = cell.userLabel.text;
NSString * chatText = cell.chatMessage.text;
// Get bounding rect of userName label
CGRect userNameFrame = [userName boundingRectWithSize:CGSizeMake(maxLabelWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:cell.userLabel.font} context:nil];
// Get bounding rect of chatMessage label
CGRect chatTextFrame = [chatText boundingRectWithSize:CGSizeMake(maxLabelWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:cell.chatMessage.font} context:nil];
// Calculate cell height from its contents
height = userNameFrame.size.height + chatTextFrame.size.height + PADDING_ABOVE_TOP_LABEL + DISTANCE_BETWEEN_TWO_LABELS + PADDING_BELOW_BOTTOM_LABEL;
return height;
You can determine the cell height dynamically so that the text appears on one line. I've given CGSize(320,568) as bounds. But you can change the size as you see fit
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *chatText = [[chatData objectAtIndex:row] objectForKey:TEXT];
CGRect frame = [chatText boundingRectWithSize:CGSizeMake(320,568) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:18.0f]} context:nil];
return frame.height; //returns cell's height after calculating the area that text needs
}
You are setting the frame of the text and calling sizeToFit:
CGSize size = [cell.textString.text sizeWithFont:cell.textString.font constrainedToSize:CGSizeMake(SET_WIDTH, MAXFLOAT) lineBreakMode:NSLineBreakByCharWrapping];
cell.textString.frame = ...;
[cell.textString sizeToFit];
This will not be working with autolayout. You need to remove setting the frame and sizeToFit completely, same goes for all other frame being set directly. If things don't work after that, it would mean some constraints are not correct. But mixing approaches always breaks layout.
For UILabels, make sure to also set preferredMaxLayoutWidth to label's width, otherwise text may not wrap correctly:
- (void)layoutSubviews
{
[super layoutSubviews];
self.label.preferredMaxLayoutWidth = self.label.frame.size.width;
[super layoutSubviews];
}
Also, your height calculating method should not just do same thing as cellForRowAtIndexPath:, if you want to use the same code, just call that other method to get a cell.
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?