UITextView offsets text differently than UILabel - ios

I am using UILabel and UITextView and they render text differently. It seems that UITextView offsets text by 4.
Below is an example where at the top is UILabel and bellow is UITextView. They both use same font. Two examples are here, one with the custom OpenSans font and one with the system's HelveticaNeue font.
UILabel is being resized after setting the text by using sizeThatFits:
label.text = text;
CGFloat width = 320 - 2 * 16; // both label and textView end up with 288 width
CGSize size = [label sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)];
CGRect frame = CGRectMake(16, 0, width, size.height);
label.frame = frame;
UITextView.textContainerInset is set to (0,0,0,0).
Any help? Here are the screenshots:
1.1 HelveticaNeue: textView offset -4 (label on top)
1.2 HelveticaNeue: aligned (label on top)
2.1 OpenSans: textView offset -4 (label on top)
2.2 OpenSans: aligned (label on top)

This works for me and eliminates the inner padding:
textView.textContainer.lineFragmentPadding = 0;
textView.textContainerInset = UIEdgeInsetsZero;

Related

How to calculate TextView height base on text

I am using the code below for calculate the height of text, then set this height for UILabel and UITextView
CGSize targetSize = CGSizeMake(300, CGFLOAT_MAX);
NSString *message = #"The Internet connection appears to be offline.";
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [message boundingRectWithSize:targetSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:FontOpenSanWithSize(14)}
context:context].size;
CGSize size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
// it will return size:width = 299 and size height 20
// => if I use this height and set for UILabel, it can display full content
// => if I use this height and set for UITextView, it can not display full content
It's work perfect for UILabel but for UITextView sometime it calculate wrong.
I think the problem happened because the padding (left, right) of UITextView is bigger than UILabel.
So how can I calculate the correct size of text for display in UITextView. Any help or suggestion would be great appreciated.
Like the description image below
With the same size (300), same font, same text but the UITextView display in 2 lines, but UILabel in 1 lines.
And my code for calculate height return 20, it not enough for display in 2 lines, so the UITextViewcan not display full content
The reason why I need to calculate the height of UITextView base on text because my UITextView is in a popup.
And the popup height will depend on the TextView height
There are two things you can try:
Set textView.textContainerInset = UIEdgeInsetsZero
Set textView.textContainer.lineFragmentPadding = 0
With these operations you can get rid of all the padding in the textView and when its width matches with the label's one the heights are also the same.
Here's a sample code you can place in an empty viewController and test it yourself:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSString *text = #"The internet connection appears to be offline.";
CGFloat width = 100.f;
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(20, 20, width, 300)];
textView.font = [UIFont fontWithName:#"AvenirNext-Regular" size:12.f];
textView.text = text;
[self.view addSubview:textView];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20 + width, 20, width, 300)];
label.numberOfLines = 0;
label.font = [UIFont fontWithName:#"AvenirNext-Regular" size:12.f];
label.text = text;
[self.view addSubview:label];
// Getting rid of textView's padding
textView.textContainerInset = UIEdgeInsetsZero;
textView.textContainer.lineFragmentPadding = 0;
// Setting height of textView to its contentSize.height
CGRect textViewFrame = textView.frame;
textViewFrame.size = textView.contentSize;
textView.frame = textViewFrame;
// Setting height of label accorting to it contents and width
CGRect labelFrame = label.frame;
labelFrame.size = [label sizeThatFits:CGSizeMake(width, HUGE_VALF)];
labelFrame.size.width = width;
label.frame = labelFrame;
NSLog(#"Label bounds: %#", NSStringFromCGRect(label.bounds));
NSLog(#"TextView bounds: %#", NSStringFromCGRect(textView.bounds));
// Visualizing final effect with borders
textView.layer.borderColor = [UIColor redColor].CGColor;
textView.layer.borderWidth = 1.f;
label.layer.borderColor = [UIColor greenColor].CGColor;
label.layer.borderWidth = 1.f;
}
Console output:
2016-09-01 14:29:06.118 stack39268477[943:243243] Label bounds: {{0, 0}, {100, 66}}
2016-09-01 14:29:06.119 stack39268477[943:243243] TextView bounds: {{0, 0}, {100, 66}}
You don't need to calculate Height of UITextview based on text.
Just change frame and set height like this:
textview.size.height = textview.contentSize.height;
This is easy solution. I hope this helps you.
This calculates the size of any string, whether or not you put them in a text view.
let frame = NSString(string: yourText).boundingRect(
with: CGSize(width: yourDesiredWidth, height: .infinity),
options: [.usesFontLeading, .usesLineFragmentOrigin],
attributes: [.font : yourFont],
context: nil)
let height = frame.size.height
Most of the answers here are hints into the right direction :-)
So, just to sum it all up...
UITextView uses a NSTextContainer (inside a private API _UITextContainerView) to do the real layout work.
This NSTextContainer(View) may have insets to the surrounding UITextView, which are set by UITextView's textContainerInset property.
The defaults for this insets seem to be:
top: 8
left: 0
bottom: 8
right: 0
The NSTextContainer itself may have additional left and right insets for the text itself. These insets are set in NSTextContainer's lineFragmentPadding property.
The default for this is 5.0.
As a result, when calculating the optimum frame size for a UITextView based on the boundingRect for some text inside that UITextView, we have to take all these insets into account:
CGSize reservedSpace = CGSizeMake((textView.textContainerInset.left + (2.0 * textView.textContainer.lineFragmentPadding) + textView.textContainerInset.right),
(textView.textContainerInset.top + textView.textContainerInset.bottom));
CGSize targetSize = CGSizeMake((300.0 - reservedSpace.width), CGFLOAT_MAX);
NSString* message = #"The Internet connection appears to be offline.";
NSStringDrawingContext* context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [message boundingRectWithSize:targetSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:FontOpenSanWithSize(14)}
context:context].size;
CGSize size = CGSizeMake(ceil(boundingBox.width),
(ceil(boundingBox.height) + reservedSpace.height));
Good luck :-)
Swift 5
A classic hack I've used to do this is create a function that takes the String as a parameter. Within the function it generates a label with the width required, infinite number of lines, and a height of "too much". Then apply sizeToFit() on the label and return the height of the frame.
func calculatedHeight(for text: String, width: CGFloat) -> CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: width,
height: .greatestFiniteMagnitude))
label.numberOfLines = 0
label.text = text
label.sizeToFit()
return label.frame.height
}
The returned height can be applied to a UITextField height anchor. Though I do recommend calculating the width dynamically based on screen size, but that's up to you.
self.textView.textContainerInset = UIEdgeInsets.zero
self.textView.textContainer.lineFragmentPadding = 0
In storyboard or xib mark textview height >=0.
If you are using text view with table view.
Calculate cell height according to content, textview will adjust it's space.

Get Frame Of line of Text of UILabel when Text Alignment in Center

My label size is W= 190 H =322 and the text in label is in center alignment like
I want an orange box character of line of text frame from UILabel
The frame of your UILabel should be the same size as I'ts content. Wrapped.
This is how you do it:
Without auto layout:
You should call sizeToFit on UILabel to shrink it to the text size, and then check I'ts frame.
textLabel.text = "#yourText";
textLabel.numberOfLines = 1;
[textLabel sizeToFit];
CGSize size = CGSizeMake(textLabel.frame.size.width, textLabel.frame.size.height);
With autolayout:
Just set "numberOfLines" to 0, and don't give the label a width constraint, and the label size will fit automatically.
EDIT
Core text:
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.text = #"Your text";
float yourFontSize = 20;
while ([label.text sizeWithAttributes:#{NSFontAttributeName:[UIFont systemFontOfSize:yourFontSize]}].width > modifierFrame.size.width)
{
yourFontSize--;
}
label.font = [UIFont systemFontOfSize:yourFontSize];

UIButton Title Label Not Filling When Text Alignment Is Centered

I have a UIButton and am trying to center the text. The below works, but it cuts off my button's titleLabel way before the end of the button. I want it to grow until the edge of the subview, i.e. a CGRect frame of (8+9,button height ,button width - 8 - 9, button height)
self.setDestinationButton.titleLabel.textAlignment = NSTextAlignmentCenter;
self.setDestinationButton.layer.masksToBounds = NO;
self.setDestinationButton.titleLabel.adjustsFontSizeToFitWidth = YES;
self.setDestinationButton.titleLabel.minimumScaleFactor = .75;
self.setDestinationButton.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
UIImageView *destinationIcon = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"grey_dot"]];
destinationIcon.frame = CGRectMake(8, 18 ,9, 9);//choose values that fit properly inside the frame of your baseButton
//or grab the width and height of yourBaseButton and change accordingly
destinationIcon.contentMode=UIViewContentModeScaleAspectFill;//or whichever mode works best for you
[self.setDestinationButton addSubview:destinationIcon];

UIScrollView size to match UITextView size

I have a UIScrollView with a block of text in a UITextView. The text could be any amount of words coming from a server.
I want the scroll view to be the correct scroll size to match the size of the UITextView.
What is the current practice to do this?
Screenshot:
UITextView is an extension of UIScrollView. So you can get its content size using contentSize propery.
You can use something like below to get the height for your textview
CGSize size = [#"Your string"
sizeWithFont:[UIFont fontWithName:#"TimesNewRomanPS-BoldMT" size:22]
constrainedToSize:CGSizeMake(500, CGFLOAT_MAX)];
_textView.frame = CGRectMake(x,y, width, size.height);
_scrollview.frame = CGRectMake(x,y, width, size.height);
or there is a littl hack you can use :p
Use a Label to get the height for your text view.
UILabel *tempLabel = [[UILabel alloc]initWithFrmae:_textView.bounds];
tempLabel.numberOfLines = 0; // Important to do
tempLabel.text = #"Your Text";
[tempLabel sizeToFit]; // This will resize your Label and now you can use it's height to manage your textView.
Now use this tempLabel's height
tempLabel.frame.size.height

Why is UIFont sizeWithFont including blank space in its calculation?

I am setting a UILabels frame based on what is returned by UIFont sizeWithFont but for whatever reason when i use a custom font, the values that are returned include some padding as seen below.
When i use boldSystemFontOfSize the text is vertically aligned in the middle (which is what i want), but when i use fontWithName i end up with padding under the text. Any reason why sizeWithFont is adding in the padding?
Heres my code...
CGRect frameLabel = label.frame;
CGSize sizeLabel = [label.text sizeWithFont:label.font];
frameLabel.size.width = sizeLabel.width;
frameLabel.size.height = sizeLabel.height;
[label setBackgroundColor:[UIColor redColor]];
** Edit **
I can calculate the top and bottom padding using this code and adjust the labels frame.origin.y to vertically center my label where it needs to be
float topPadding = [label.font ascender] - [label.font capHeight];
float bottomPadding = [label.font lineHeight] - [label.font ascender];
Font is the only possible cause of this padding, but if you only need one-line labels, don't waste your time editing the font, just reduce the label's height by those few pixels after setting a proper frame by doing something like this:
label.frame = CGRectInset(label.frame, 0, bottomPadding);
Also, instead of:
CGRect frameLabel = label.frame;
CGSize sizeLabel = [label.text sizeWithFont:label.font];
frameLabel.size.width = sizeLabel.width;
frameLabel.size.height = sizeLabel.height;
You can just call:
[label sizeToFit];

Resources