Precise calculation of the Height of a UITableViewCell Based on Text - ios

I am working on an ios application, and using autolayout I am trying to create a Table View with different row heights.
The layout of the prototype cell is as follows:
I have the main cell (in black) inside it I have a UIView (in red) and inside that view a UILabel (in blue)
The Autolayout constraints I added are as shown on the figure:
The UIView has the following Constraints:
80 to the left edge of the cell
20 from the right edge of the cell
15 from the top edge of the cell
15 from the bottom edge of the cell
The UILabel has the following Constraints:
20 to the left edge of the UIView
15 from the right edge of the UIView
10 from the top edge of the UIView
10 from the bottom edge of the UIView
I need the UILabel to be dynamic in height based on the text size inside of it.
To do that, I have done the following:
Set the number of lines of the UILabel to 0
Set the font to Helvetica Neueu with size 15 (in the interface builder)
Set the Lines Break to "Word wrap" (in the interface builder)
in the view Controller, I have implemented the following:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//get the item
ListItem *item = (ListItem*) self.items[indexPath.row];
//calculate the label size based on the item title that we will display
CGSize textSize = [item.title sizeWithFont:[UIFont fontWithName:#"HelveticaNeue" size:15.0] constrainedToSize:CGSizeMake(tableView.frame.size.width - 135.f, 9999.f) lineBreakMode:NSLineBreakByWordWrapping];
//return the height + the 50 to accomodate with the layout
return textSize.height + 50.f;
}
Basically what I'm doing is getting the text I need to display (item.title) and I call sizeWithFont to calculate how much I need space for that label and return it.
in the sizeWithFont method I pass the font I am using for the title [UIFont fontWithName:#"HelveticaNeue" size:15.0] and then I constraint the size of the calculation based on the pictures constraints, by getting the width of the tableView and subtracting the margins to get to the label
For the width: I substract 80 (for the UIView left) and 20 (for the label left) and 15 (for the label right) and 20 for the UIView Right)
For the Height I put 9999 as I don't need a constraint on it for the calculations.
After I get the size of the label needed I returned the exact height plus the 15, 10 ,10 and 15 for the vertical margins of the label (total = 50) return textSize.height + 50.f;
The problem:
Although I am doing exact calculation, but when running the app it is not 100% precise. While most cases I get a precise height, but in some cases (especially when we have for example 3 lines in the label, and the 3rd line has one one word), the cell get a height corresponding to 2 lines only and cutting the third one. increasing the height by trial and error might be possible but it will also affect the height of the cells that were displayed well.
So my question is, what am I doing wrong with my calculations? and is there a way to have a dynamic height for the cells based on the text in the label, when using autolayout?
Thanks

Any chance the fonts don't match?
You'd be calculating based on Helvetica Neue Regular, I think.
What's the font of the UILabel? Being off by one word makes me think font metrics.
I don't see another error

If you want precise, use the prototype cell approach. It is inherently precise because you're getting the height of an actual cell.
Using this approach for dynamic label height generally involves setting the label's text, calling sizeToFit and then getting the label's intrinsicContentSize. Here is an example implementation.

Related

How to break line of a UILabel dynamically with text?

I have a UILabel and I want to show some text in this label. I want to increase the label width at most 70% of the full screen of my device. If text length of that label doesn't fit this 70% of size then the label automatically goes to the next line as long as the text length. Every time the label length cross the 70% width of main screen then lines break as well. I have tried several ways but unable to solve yet. Please help me to solve this.
Thanks in advance;
Drag a label to your storyboard and add top and leading constraints to it.
Now select the label and control drag to the view holding the label (in your case view of ViewController) you will see the pop up and then select equal width
Now your Label's width is equal to your view's width :) That's not you want you want ur label width to be 70% of your view. So select the equal constraint of label, go to property inspector and change the multiplier to 0.7
Now your label width is 70% of your view!
But you don't want it to be 70% always. It can be at max 70% of screen, so
now change the relationship of constraint from being equal to less than or equal to.
select label and change number of lines to 0.
That's it :) have fun :)
Sample O/P:
When text is short - vs - long:
- - -
EDIT:
Not using a storyboard? Not a problem; write the same constraint programmatically and apply it to label simple enough. If you need help lemme know :)
EDIT:
As you have specified that you want to leave the gap at the beginning of each line in label you can achieve it by using Edge insets
- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = {0, 5, 0, 0};
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
You must have forgotten to increase the label's height.
The code below is for allowing the UILabel to have multiple lines:
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
Then you have to make sure the UILabel's frame has enough height to show the lines. You can achieve this by calculating the required height for the given text (NSString):
NSString *text = #"YourText";
CGFloat your70Width; // whatever your width is
CGSize constraintSize = CGSizeMake(your70Width, MAXFLOAT);
UIFont *yourLabelFont; // whatever your font is
CGRect requiredFrame = [text boundingRectWithSize:constraintSize options:NSStringDrawingUsesFontLeading attributes:#{NSFontAttributeName:yourLabelFont} context:nil];
// Keeps the old x,y coordinates and replaces only the width and height.
CGRect oldLabelFrame = label.frame;
label.frame = CGRectMake(oldLabelFrame.origin.x, oldLabelFrame.origin.y, requiredFrame.size.width, requiredFrame.size.height);
Now the label will be shown nicely in multiple lines.
To increase the height of the label according to the content if you are using storyboard. Give the label FOUR constraints (Top, Bottom, Leading, Trailing) then go to Attribute Inspector and make lines to 0 and in line break do it WORD WRAP.

ios7 AutoLayout tableViewCell

I currently have a custom UITableViewCell with a number of labels in it. Not all of those labels will necessarily have a value but they could do - and they may be multiline (At least 2 labels will always be visible). If there isn't any text then the label should collapse. What I'm trying to achieve is that each label only takes up the height it needs and they all butt up to each other. I've got all of this working, except I finally want a bottom margin of 11 between the last label and the cells content view with the cell height adjusted accordingly. This is the only bit where I can't seem to get my constraints working correctly.
I correctly return the right height by creating a dummy cell, and in my cellForRowAtIndexPath method I try to set the cell and the cell's contentview frame accordingly. But the cell height that is rendered on the screen is always the same as it was defined in the xib.
I know there's a lack of code here, but all my constraints have been define in IB and each label has a trailing and leading space for with and a top space of 0 to the label above it. The top label has a top space to create a top margin to the content view, but as soon as I do this on the bottom label it stretches the height of the labels to accommodate all the constraints.
I've seen various tutorials and questions on here that relate to 2 labels where 1 might be a variable size, but I haven't been able to adapt any solutions for what I'm trying to achieve.
Okay,
Get as many labels you want (suppose you want 2 labels inside the cell,)
Create 2 labels with height 1 in storyboard, or if you are programmatically adding them, then you can do.
UILabel *myLabel1 = [[UILabel alloc]initWithFrame:[CGRectMake:(x,y,width,1)]; // assuming you know their x,y and width respectively as you need them.
Now add constraints to the labels (Be sure you make them ambiguous or else orientation would mess it up, ):
3 a. Height -(should be equalto OR greater than => 1).
b. Width - [Set it a width],Make sure it has a priority more than others but less than height
c. Pin to trailing and leading space, as well as superview top and bottom.
d. If there are two Labels make sure you click both of them and add a Vertical or horizontal space according to your needs.
I think that would make it ambiguous. (I am telling these as you wrote its about autolayout, so despite of orientation this would never get any layout problems).
Add a snippet that would calculate the height of the Label when you know the string.
CGSize constrainedSizeOfMessege = CGSizeMake(widthOfLabel, 9999);//150,180 anything you like
NSDictionary *attributesDictionaryMessege = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"System Italics" size:16.0], NSFontAttributeName,
nil];
NSMutableString *myString = [[NSMutableString alloc]initWithString:[[friendsInstanceArray objectAtIndex:indexPath.row] myString] attributes: attributesDictionaryMessege];
CGRect requiredHeightMessege = [myString boundingRectWithSize:constrainedSizeOfMessege options:NSStringDrawingUsesLineFragmentOrigin context:nil];
Add the above code in
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Now, calculate the height of the Cell, the height of all the labels if they are 1 height then
Return the height of the Cell in storyboard + height of the Expected Label Size.
So The method would look like.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize constrainedSizeOfMessege = CGSizeMake(widthOfLabel, 9999);//150,180 anything you like
NSDictionary *attributesDictionaryMessege = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"System Italics" size:16.0], NSFontAttributeName,
nil];
NSMutableString *myString = [[NSMutableString alloc]initWithString:[[friendsInstanceArray objectAtIndex:indexPath.row] myString] attributes: attributesDictionaryMessege];
CGRect requiredHeightMessege = [myString boundingRectWithSize:constrainedSizeOfMessege options:NSStringDrawingUsesLineFragmentOrigin context:nil];
int heightOfMessege = requiredHeightMessege.size.height;
//if cell height is 50 then return 50+heightOfMessege
return 50+heightOfMessege;
}
And YES POSITIVELY set, mylabel.numberOfLines = 0;
Hope this helps you out
If you want to stick labels on their position including with the empty space than give a single space in label's title text which will not make their text empty and than label will stick to positions.

heightForRowAtIndexPath with custom cell and label [duplicate]

This question already has answers here:
Calling heightForRowAtIndexPath from within cellForRowAtIndexPath?
(3 answers)
Closed 10 years ago.
If I'm making a custom UITableViewCell and I need it to have a variable height depending on the length of the input (for instance, for a Twitter reader or something), how can I make this work?
I've found lots of other examples here that can set it for a the standard, non-custom cell, but my cell's main text label is smaller, etc., so when I try to use any of those methods on my cell, they give a variety of weird results (text overlapping the bottom of the cell, etc.)
Is there a standardized way of designing the cell (for example, how tall should I make it in Interface Builder?), and let's say my label was half the width of that cell.. how would I go about calculating the height the cell would need to be to display the string loaded into that label? Here's a method that I found here which works fine on the normal cell, but screws up custom ones with weird heights, overlapping text, etc: (I have absolutely NO idea what the 300 / 200000 do here, if anyone could explain that I'd be grateful, too!)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize textSize = {300.f, 200000.0f};
CGSize size = [[_quoteStringsFromPlist objectAtIndex: indexPath.row] sizeWithFont: [UIFont systemFontOfSize:12.0f] constrainedToSize: textSize lineBreakMode: NSLineBreakByWordWrapping];
size.height += 5.0f;
float result = MAX(size.height, 32.0f);
return result;
}
Something like this should work.
CGRect labelFrame = // enter default frame of the label here
UIFont *labelFont = // enter label font here
CGFloat labelBottomMargin = // enter the space between the bottom of the label and the bottom of the cell here
CGSize size = [labelString sizeWithFont:labelFont constrainedToSize:CGSizeMake(labelFrame.size.width, MAX_FLOAT)];
size.height += labeFrame.origin.y;
size.height += labelBottomMargin;
size.height = MAX(size.height, 32.0f);
return size.height;
First, head over to your xib file and see how large are the top/bottom margins of your text area, and how wide it is.
After that, you need to calculate the height needed for it with the width you have available, and then add that value to the top/bottom margins your text area already has.
The result should look correctly regardless of the size of each cell in IB or the text you are trying to put in them.
EDIT
Imagine your cell size is {700, 300} in your IB, and your text area is located on {{100, 100}, {300,100}}, your text area has a 100px margin top and 100px margin bot, and it's width is 300.
When you calculate the height you require for your text, you calculate it with an available 300 width. It will return a size, something like {300, 250}. That 250 is the height required by your text, but your cell has other stuff in it and you need to add those top and bot margins to that, so the result is 450.
Remember to set an autoresizing mask or autolayout so that your text area stretches vertically and the margins are fixed (UIViewAutoresizingFlexibleHeight)

Calculate the width of a grouped tableview cell

I'm trying to place two UILabel on the same vertical axis within a UITableViewCell, and have them take up 50% of the cell's width, when the cell is a grouped table cell.
I've tried self.frame.size.width / 2, but the width that self.frame.size.width looks like the width of the entire cell as if it were not a grouped cell. In other words, it seems to be the width of the whole screen, in a full screen table.
I also compared self.frame.size.width to self.contentView.frame.size.width and both appear to be the same, and are not the actual width of the grouped cell, I don't believe.
When I initialize the UILabel with a specified frame, and specify the x-axis to as 0, the label does place itself on the left edge of the grouped cell, which is great! That's what I want.
But, for the 2nd UILabel that I wish to be to the right of the 1st label, on the same horizontal axis, I do not know how to calculate where to start for the label's frame's x-axis.
Likewise, I do not know how to calculate width for either of these labels, because I can't seem to get the width of the grouped cell.
I feel like there must be a way to get the exact width, because for the first label, giving the label's frame an x-axis of 0 puts it right on the edge of the cell, which is exactly as i need. So I need like internally, this code know the width of that cell too, somewhere.
Ok, I figured out a good way to achieve this. If you're subclassing UITableViewCell and want to adjust the cell's sub-views, then you need to perform these calculations and frame adjustments inside of the layoutSubviews: method. Inside of this method, the cell's contentView will be the intended width, even when using a grouped cell. Below is what my layoutSubviews function looks like ...
- (void)layoutSubviews {
[super layoutSubviews];
// half of the cell's content view width
CGFloat halfCellWidth = self.contentView.frame.size.width / 2;
// the dimensions of the message label, primarily for the height
CGSize mesageLabelDimensions = [_message.text sizeWithFont:[UIFont systemFontOfSize:12]
constrainedToSize:CGSizeMake(self.contentView.frame.size.width - (LEFT_RIGHT_MARGIN * 2), 500.0)];
// adjust the frames of the cell labels
_source.frame = CGRectMake(LEFT_RIGHT_MARGIN, TOP_BOTTOM_MARGIN, halfCellWidth - LEFT_RIGHT_MARGIN, LABEL_LINE_HEIGHT);
_timestamp.frame = CGRectMake(halfCellWidth, TOP_BOTTOM_MARGIN, halfCellWidth - LEFT_RIGHT_MARGIN, LABEL_LINE_HEIGHT);
_message.frame = CGRectMake(LEFT_RIGHT_MARGIN, LABEL_LINE_HEIGHT + (TOP_BOTTOM_MARGIN * 2), mesageLabelDimensions.width, mesageLabelDimensions.height);
}

How to create a multi-line UILabel, with a maximum width?

I'm writing a simple IRC client that I'm modeling after Twitter's iOS app appearance. I've taken a screenshot of the Twitter app, for reference:
It looks like a simple table view with a few labels inside of each cell. So, in my app, I am programmatically creating a table and the cell formatting. My custom cell has only two labels in it, which I have positioned one on top of the other. The top label is a simple 1-liner. The bottom label I would like to contain longer messages, and need it to word-wrap to multiple lines while staying within my specified width.
How do I achieve this?
So far, I've tried explicitly setting the frame of the label to the dimensions that I want, but it does not word-wrap, if this is all I do. It just flows out of the cell horizontally. I then tried calling sizeToFit, within the tableView:cellForRowAtIndexPath: function, for this label, but it appears to word-wrap at a very small width - the text wraps after like two or three letters and then flows out of the cell vertically.
I can't seem to figure out how to get the text within the label to wrap after a specified width. Any ideas?
My custom cell class: https://github.com/ryancole/pound-client/blob/master/pound-client/views/MessageListCell.m
The cellForRowAtIndexPath function: https://github.com/ryancole/pound-client/blob/master/pound-client/controllers/MessageListViewController.m#L62-L84
Edit 1:
To demonstrate what happened when I set numberOfLines to 0, for unlimited, I have attached a screenshots of that being called. It wraps after a few characters, instead of first taking up the specified width of the UILabel's frame. This is being set prior to called sizeToFit.
You need to set numberOfLines to the number of lines you want, or 0 which allows for an unlimited number of lines (the default is 1). You might also need to set the lineBreakMode to NSLineBreakByWordWrapping (although that might be the default).
After Edit: If you want the text to start at the top, then I think you'll have to use variable height cells, and not set an explicit size for your custom cell. I did it this way in one of my projects:
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = _objects[indexPath.row][#"detail2"];
CGSize labelSize = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(300, 300000) lineBreakMode:NSLineBreakByWordWrapping];
CGFloat h = labelSize.height;
return h + 50;
}
The label I create here, is just for calculating the height of the row, it's discarded after this method ends. The width of the cell is determined by the 300 argument I have in the constrainedToSize: parameter. The +50 was just a fudge factor I added to get my cells looking right -- you'd probably want to mess with that number to get what you want. In my custom cell class, I used initWithStyle:reuseIdentifier, and didn't set any size.

Resources