I've frustrated myself with this question for a couple of days. I'm trying to add a UILabel to a UITableViewCell. I want the UILabel to span the entire width of the cell, minus 5 or 10 on both the right and left sides for looks. My problem is in programmatically determining the size of the cell's frame in which to place the label. No matter which UITableViewStyle I use, the cell.contentVew.frame.size.width value is nowhere near the width of the cell frame itself.
For example, in the table I am constructing, I can achieve my desired result by subclassing UITableViewCell and creating a UILabel with a manually determined width (through just trial and error) by:
CGRectMake(10, 12, 397, self.contentView.frame.size.height);
But it's that 397 number that's vexing me. I want a way to programmatically determine what it should be for any width table or style. This should be a simple process by just determining the width of the entire frame of the cell and then subtracting 10 or 20 so the UILabel's edges don't actually touch the edge of the cell.
However, if I set the tableViewStyle to UITableViewStyleDefault and then try:
NSLog(#"Width: %f", self.contentView.frame.size.width);
I get 320. If I set the style to any of the other three styles, the returned number is 302. Even the 320 number isn't anywhere near the width of the cell frame (as with my manually determined number of 397).
What value do I need to access that will return the entire width of the cell's drawing frame? I'm sure, as with most vexing problems, the solution will make me want to slap myself on the forehead, but I'm to the point where I'm ready for it now.
EDIT for more info:
One clarification to anyone interested. This question of mine pertains primarily to a Grouped style table. For a plain style, the answer to my question above can be determined simply in the cellForRowAtIndexPath method by:
CGFloat cellWidth = [tableView rectForRowAtIndexPath:indexPath].size.width;
The problem I'm having is that the rectForRowAtIndexPath method returns the width of the frame in which the cell is drawn, which is fine for a plain style table since the cell width is the entire width of the frame. However, in a grouped table, the width of the cell is less than the width of the frame in which it is drawn, so this method will return a number that is quite a bit wider than the width of the cell. It's possible that the width of the cell in a grouped table style is a fixed number less than the width of the table frame, so this might be the way to solve the problem. I'll investigate that and answer my own question here if that's the case.
I have determined my own answer, and I hope it helps anyone faced with the same issue. The calculation of the margin of a grouped tableView I found on this StackOverflow answer.
This code will provide a label within a tableView cell that spans the cell with a margin between the two edges of the cell, and centered vertically within the cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *label;
CGFloat groupedStyleMarginWidth, tableViewWidth;
UIFont *labelFont = [UIFont boldSystemFontOfSize:17.0]; // Set to whatever you like
NSString *labelText = #"Test String";
// Calculate the margin between the cell frame and the tableView
// frame in a grouped table view style.
tableViewWidth = tableView.frame.size.width;
if (tableView.style == UITableViewStyleGrouped) {
if (tableViewWidth > 20)
groupedStyleMarginWidth = (tableViewWidth < 400) ? 10 : MAX(31, MIN(45, tableViewWidth*0.06));
else
groupedStyleMarginWidth = tableViewWidth - 10;
}
else
groupedStyleMarginWidth = 0.0;
if (cell == nil) {
CGRect tableViewRect;
CGRect labelRect;
CGFloat x, y, w, h, labelMargin;
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// Retrieve the rect of the table view.
tableViewRect = [tableView rectForRowAtIndexPath:indexPath];
// Set whatever margin around the label you prefer.
labelMargin = 10;
// Determine rect values for the label.
x = tableRect.origin.x + labelMargin;
// Calculate width of label
w = tableRect.size.width - (groupedStyleMargin * 2) - (labelMargin * 2);
// Calculate height of table based on font set earlier.
h = [labelText sizeWithFont:font].height;
// Calculate y position for the label text baseline to center
// vertically within the cell.
y = (tableRect.origin.y / 2) - (h / 4);
labelRect = CGRectMake(x, y, w, h);
label = [[UILabel alloc] initWithFrame:labelRect];
label.text = labelText;
label.tag = 0;
[cell.contentView addSubview:stepLabel];
[label release];
}
else {
label = (UILabel *)[cell viewWithTag:0];
}
Sounds like this would best be handled by auto layout constraints nowadays.
Related
I'm trying to get my UITableViewCell.textLabel property to autofit text both vertically and horizontally.
Right now my tableView is not scrollable, so the cell frame is not dynamic. I need the textLabel property to word wrap and resize to a set # of lines. Setting the numberOfLines to 0 isn't working because it cut off because my cell height is smaller than the textLabel final height. Using an arbitrary number for numberOfLines cuts off the text.
How can I resize my textLabel to fit within the frame of the cell?
Code below
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = (UITableViewCell*)[tableView #"mycell"];
cell.textLabel.text = #"LOTS OF TEXT";
cell.textLabel.textAlignment = NSTextAlignmentCenter;
[cell.textLabel textRectForBounds:cell.bounds limitedToNumberOfLines:0];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.textLabel.numberOfLines = 0;
cell.textLabel.adjustsFontSizeToFitWidth = YES;
cell.textLabel.minimumScaleFactor = .5;
}
EDIT: If the cells are a set height this is straightforward. You will have to set a custom cell and textLabel in it, as I don't think you can't modify the textLabel in the basic or other preset cells the way you need to. Just add a text label and set constraints linking it to the four sides, with the minimum amount (can be zero). Labels automatically center text vertically if there is extra space, so making the label too tall shouldn't be a problem.
Original answer:
If you're not using tableView self-sizing cells, maybe to support iOS 7, then it won't matter what you do in the cell because the issue is in the tableView row heights. If a single label the classic boundingRect method will work. in tableView:heightForRowAtIndexpath: do
NSString *stringFromDataSource = #"Text For Label";
CGSize potentialSize = CGSizeMake(CGRectGetWidth(self.tableview.frame), CGFLOAT_MAX);
NSStringDrawingOptions optionsForMultiline = NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
NSDictionary *attributes = #{
NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody]
}; //or whatever
CGRect rectForText = [stringFromDataSource boundingRectWithSize:potentialSize
options:optionsForMultiline
attributes:attributes
context:nil];
return CGRectGetHeight(rectForText);
Note that in your question, your various options for sizing the cell aren't going to be used if the textLabel is expanding with autolayout, unless there is a constraint capping the maximum size, at which point minimumScaleFactor and so on will kick in.
I'm trying calculate the height of each cell in my table. Currently, I use this.
CGSize maximumSize = CGSizeMake(tableView.frame.size.width, UILayoutFittingCompressedSize.height);
CGFloat height = [offscreenCell.contentView systemLayoutSizeFittingSize:maximumSize].height;
return height;
The problem is that the cell has many labels with a date string label at the top of it and then multiple labels added below that listing the items for that date. This date label is being cut off for some reason in some of the cells. I think it might have to do with the label's string wrapping to a second line and the height not being calculated correctly.
Any ideas? Thank you.
Get a cell, set it's properties(date label, etc.) call layoutIfNeeded and then calculate this height:
MyCustomCell* cell = [myTableView dequeReusableCellWithIdentifier:#"CellId"];
for( NSString* text in arrayOfTexts )
{
[cell setDateLabelText:text];
[cell layoutIfNeeded];
CGSize maximumSize = CGSizeMake(tableView.frame.size.width, UILayoutFittingCompressedSize.height);
CGFloat height = [offscreenCell.contentView systemLayoutSizeFittingSize:maximumSize].height;//now store this height and use it in height for row at index path
}
I have a summary cell that has it's height calculated using systemLayoutSizeFittingSize. It mostly works as expected. The height is determined by three multi-line labels (title, author, genre) and an ratings image view, with the outer elements tacked to the contentView.
When the title label overflows into the next line, it sizes appropriately. However when the author label overflows, it doesn't seem to increase the size appropriately.
All the compression resistances on the labels and image view are maxed out at 1000. There is an lower priority constraint on the bottom of the thumbnail to the left, in case the content to the right is smaller than the thumbnail. (#750, bottom == 8 from superview bottom). The ratings image view has a constraint to the bottom of the superview as well (#1000, bottom >= 8 from superview bottom).
Interesting. So I ended up fixing this by always resetting the reference cell frame height before updating the content and calculating the height. Not entirely sure why this step is needed. I have a couple guesses around the autoresize constraints on the contentView taking precedence:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case TableSectionSummary: {
TCAnswerDetailAppSummaryCell *cell = self.summaryCell;
CGRect oldFrame = self.summaryCell.frame;
cell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, mTCTableViewFrameWidth, 400);
[cell configureWithThirdPartyObject:self.app];
[cell updateConstraintsIfNeeded];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 1;
return height;
}
case TableSectionDetails:
return [TCAnswerDetailBasicCell heightForCellWithTableWidth:mTCTableViewFrameWidth withLabelString:self.app.appDescription withDisclosureArrow:NO];
}
return 0;
}
I have a custom class created for custom cells for my table view. There's UILabel for messages. I'm using storyboard to set the constraints, pinning the label to the top and right side of the table cell, I found that doing this causes the label to resize to fit the content. Now I can't figure out how to add padding to the label because anything done in cellForRowAtIndexPath doesn't work since auto layout is selected. I've seen many examples that do CGRectMake with float values but they don't work and I think it's because of auto layout. Is there any solution for this?
I'm not even sure what code to show for this...so here goes something:
How I'm getting the height of the message...the cell is set for 220 width...cellForRowAtIndexPath:
static NSString *cellIdentifier = #"chatCell";
ChatCell *cell = (ChatCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[ChatCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
info = [receivedData objectAtIndex:indexPath.row];
message = [info objectForKey:#"message"];
CGSize constraint = CGSizeMake(220, 2000);
CGRect rect = [message boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:12.0f]} context:nil];
CGSize size = rect.size;
With that information, I set the table row heights in heightForRowAtIndexPath. But adjusting the frame of the label cell.messageLabel gives me no result. Here's something else I tried in the cellForRowAtIndexPath:
UIView *messageFrame = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 220, size.height)];
[messageFrame addSubview:cell.messageLabel];
[cell addSubview:messageFrame];
But this is the result in the simulator:
Everything is moved up and the whole text doesn't show. Is there a simple solution for this or do I have to rethink my entire code?
ANSWER:
What I did was add a UIView in storyboard and put it's constraints to the bottom of the cell. Since I was adjusting the cell height based on content bounds function from ios7, setting the constraint to the bottom automatically stretched the UIView all the way to the bottom. Playing with the message label width, I got to have padding. Hope this helps someone.
I am trying to create multi-line dynamic UILabels in UITableViewCells. I have a custom UITableViewCell that has a 'comment' label. The cell and the label are created in storyboard.
I can compute the heights of the UITableViewCells properly based on the multi-line data to be stored in the UILabel (using heightForRowAtIndexPath). However, my problem lies in the actual UILabel content. The UILabel content will display only 1 line of data on table load. However, once a cell containing multiline UILabel data moves offscreen and comes back on screen, the multi-line data appears properly in the UILabel with multiple lines. Is there any way to fix this so that the multi-line data appears properly on table load?
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cCell = (CustomCell *)cell;
MyObject = [myArray objectAtIndex:indexPath.row];
cCell.commentLabel.frame = CGRectMake(65.0f, 28.0f, 243.0f, 200.0f);
cCell.commentLabel.text = MyObject.multi_line_text_data;
cCell.commentLabel.adjustsFontSizeToFitWidth = NO;
cCell.commentLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
cCell.commentLabel.font = [UIFont systemFontOfSize:13.0];
cCell.commentLabel.lineBreakMode = NSLineBreakByWordWrapping;
cCell.commentLabel.numberOfLines = 0;
[cCell.commentLabel sizeToFit];
}
Thanks!
Since you're doing this in the storyboard, you can set the necessary label properties there (lineBreakMode and number of lines). Just give the label a specific width constraint and constraints to the top, bottom, and left sides of the cell. Then, in code use sizeWithFont:constrainedToSize:lineBreakMode: in heightForRowAtIndexPath: to calculate the appropriate height for the cell based on the content of the label -- the label, because of its constraints, will expand along with the cell to the proper size. Something like this:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize rowSize = [self.theData[indexPath.row] sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(260, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
return rowSize.height + 30;
}
Here, 260 was the width I gave my label in IB, and the 30 is a fudge factor (determined empirically) to account for padding above and below the label.
I met the same problems. Unchecking Autolayout can fix it.