iOS7 UITextView contentsize.height alternative - ios

I'm porting one of apps from iOS 6.1 to iOS 7. I'm using a layout where there's a UITextView that has a fix width, but it's height is based on its contentsize. For iOS 6.1 checking the contentsize.height and setting it as the textview's frame height was enough, but it doesn't work on iOS 7.
How can I then create a UITextView with a fixed width, but dynamic height based on the text it's showing?
NOTE: I'm creating these views from code, not with Interface Builder.

With this following code, you can change the height of your UITextView depending of a fixed width (it's working on iOS 7 and previous version) :
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text andWidth:(CGFloat)width
{
UITextView *textView = [[UITextView alloc] init];
[textView setAttributedText:text];
CGSize size = [textView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
With this function, you will take a NSAttributedString and a fixed width to return the height needed.
If you want to calculate the frame from a text with a specific font you, need to use the following code :
- (CGSize)text:(NSString *)text sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size
{
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0"))
{
CGRect frame = [text boundingRectWithSize:size
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:font}
context:nil];
return frame.size;
}
else
{
return [text sizeWithFont:font constrainedToSize:size];
}
}
You can add that SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO on your prefix.pch file in your project as:
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
You can also replace the previous test SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) by :
if ([text respondsToSelector:#selector(boundingRectWithSize:options:attributes:context:)])‌

This worked for me for iOS6 and 7:
CGSize textViewSize = [self.myTextView sizeThatFits:CGSizeMake(self.myTextView.frame.size.width, FLT_MAX)];
self.myTextView.height = textViewSize.height;

In iOS7, UITextView uses NSLayoutManager to layout text:
// If YES, then the layout manager may perform glyph generation and layout for a given portion of the text, without having glyphs or layout for preceding portions. The default is NO. Turning this setting on will significantly alter which portions of the text will have glyph generation or layout performed when a given generation-causing method is invoked. It also gives significant performance benefits, especially for large documents.
#property(NS_NONATOMIC_IOSONLY) BOOL allowsNonContiguousLayout;
disable allowsNonContiguousLayout to fix contentSize :
textView.layoutManager.allowsNonContiguousLayout = NO;

Use this little function
-(CGSize) getContentSize:(UITextView*) myTextView{
return [myTextView sizeThatFits:CGSizeMake(myTextView.frame.size.width, FLT_MAX)];
}

My final solution is based on HotJard's but includes both top and bottom insets of text container instead of using 2*fabs(textView.contentInset.top) :
- (CGFloat)textViewHeight:(UITextView *)textView
{
return ceilf([textView.layoutManager usedRectForTextContainer:textView.textContainer].size.height +
textView.textContainerInset.top +
textView.textContainerInset.bottom);
}

There are simplier solution, using this method:
+(void)adjustTextViewHeightToContent:(UITextView *)textView;
{
if([[UIDevice currentDevice].systemVersion floatValue] >= 7.0f){
textView.height = [textView.layoutManager usedRectForTextContainer:textView.textContainer].size.height+2*fabs(textView.contentInset.top);
}else{
textView.height = textView.contentSize.height;
}
}
UPD: working just for displaying text (isEditable = NO)

_textView.textContainerInset = UIEdgeInsetsZero;
_textView.textContainer.lineFragmentPadding = 0;
Just don't forget the lineFragmentPadding

simple solution - textView.isScrollEnabled = false
works perfect when inside another scroll view, or tableView cell with UITableViewAutomaticDimension

#Ana's, #Blake Hamilton's solution in swift.
var contentHeight: CGFloat = textView.sizeThatFits(textView.frame.size).height
The good thing for me was that this also returns the correct contentSize, when isScrollEnable is set to false. Setting to false returned the text view's frame size instead of the content size.

Related

UINavigationBar - Autowidth

Having a few issues with the auto width of a custom class I'm calling. I have it working for height, however the width continually truncates at 320px wide. I guess I could hard code each value of the screen but that's madness. I'm calling the custom class into the Global Navigation View Controller.
#implementation MyCustomNav
- (CGSize)sizeThatFits:(CGSize)size {
CGSize newSize = CGSizeMake(self.frame.size.width,100);
return newSize;
}
#end
I have also tried;
CGSize newSize = CGSizeMake(self.bounds.size.width,100);
I get the same result with the UINavigatorBar truncating on each end. Below screen shot.
Now I did read somewhere that this is an iOS 8 bug. But I haven't confirm that yet.
Any Ideas?
Okay I got it working. With iOS8 I found that it required a little different formatting but in the end this worked. The key is to set the bounds with the UIScreen mainScreen. Also use bounds it seems to work better.
//Adjust the height of the NavigationBar
- (CGSize)sizeThatFits:(CGSize)size {
CGSize newSize = CGSizeMake(self.frame.size.width,53);
[self setFrame:CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,60)];
return newSize;
}
Hope that helps someone.

Adjusting the height of the UITextField with editing using iOS Autolayout

How do we increase the height of UITextField while editing using autolayout. I have a UIScrollView with four UITextField's. Currently all the textfield's are all of constant height. I need only a particular textfield to grow its height based on the text while editing.
You need to set height constrain for each text field.
And on edit did end method you need to identify the textfield by tag and than change the constant of the appropriate textfield.
for ex. heightTxt1,heightTxt2,heightTxt3,heightTxt4 are constraint.
Than change while edit did end called for textfield 1
heightTxt1.constant= (count height on the basis of text and font style,size);
This should resolve your issue,
Step 1: Set no of lines in your label as 0
Step 2: Add this method in your class file
//-- Dynamic label frame depend on text
-(CGRect)getLabelHeightForIndex:(UILabel *)label label_string:(NSString *)label_string
{
CGSize maxSize = CGSizeMake(label.frame.size.width, 999);
CGRect contentFrame;
CGSize contentStringSize;
NSString *version = [[UIDevice currentDevice] systemVersion];
NSString *contentStr = label_string;
if ([version floatValue] < 7.0)
{
contentStringSize = [contentStr sizeWithFont:label.font constrainedToSize:maxSize lineBreakMode:label.lineBreakMode];
}
else
{
NSDictionary *contentDic = [NSDictionary dictionaryWithObjectsAndKeys:label.font, NSFontAttributeName, nil];
contentStringSize = [contentStr boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:contentDic context:nil].size;
}
contentFrame = CGRectMake(label.frame.origin.x, label.frame.origin.y, label.frame.size.width, contentStringSize.height);
return contentFrame;
}
Step 3: Access dynamic frame for label using following code
//-- Get dynamic label height from given string
CGRect contentFrame = [self getLabelHeightForIndex:Label label_string:Label.text];
Label.frame = contentFrame;
CGFloat LabelHeight = contentFrame.size.height;
First off, you will want to store the height constraint for that specific UITextField in a property.
For example: #property (nonatomic, weak) IBOutlet NSLayoutConstraint *specificTextFieldHeightConstraint;
Then, implement -[UITextFieldDelegate textFieldDidBeginEditing:] inside your UIViewController subclass:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
specificTextFieldHeightConstraint.constant = expandedHeightGoesHere;
}
Finally, you probably want to return the height back to normal by implementing -[UITextFieldDelegate textFieldDidEndEditing:] inside your UIViewController subclass:
- (void)textFieldDidEndEditing:(UITextField *)textField {
specificTextFieldHeightConstraint.constant = originalHeightConstant;
}
If you set equal heights constraints across all of the UITextFields, you will need to set the priority of the equal height constraint that is attached to specificTextField to low when editing, and set it back to high when editing ends. You will still need a height constraint on specificTextField, but it should have a lower priority than the equal height constraint when specificTextField is not editing, and it should have a higher priority than the equal height constraint when specificTextField is editing.

Showing an image only if content view of UITextView is larger than its fame

I am doing a project in which I want show an image if the text doesn't fits to the UITextView frame.
I am using the following code
if (self.textView.contentSize.height >= self.TextView.bounds.size.height)
{
self.imageView.hidden = NO;
}
I am using Autolayout also. Does it cause such issues
And one more thing is that, when I print NSLog, the value of contentSize.height was NOT changing according to the text UITextView was showing.
You can use this method to check the height of multi line text according to the font size used
+ (CGSize) calculateLabelHeightWith:(CGFloat)width text:(NSString*)textString
{
CGSize maximumSize = CGSizeMake(width, 9999);
CGSize size = [textString sizeWithFont:[UIFont fontWithName:#"Helvetica" size:24]
constrainedToSize:maximumSize
lineBreakMode:UILineBreakModeWordWrap];
return size;
}
you need to know
width of text feild
string
font
font size
iOS7
CGRect textRect = [text boundingRectWithSize:<text Feild Size>
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont stuff here]}
context:nil];
CGSize size = textRect.size;
yes, if you assign text to textView and check its size in the next instruction in viewControllers viewDidLoad, it wont work.It will just give you what contentSize you had set.
Check the contentSize in viewDidAppear of ViewController, and it will work.
It worked for me.
Hope this helps,

what to use instead of scrollRangeToVisible in iOS7 or TextKit

In previous versions of iOS, my UITextView will scroll to the bottom using
[displayText scrollRangeToVisible:NSMakeRange(0,[displayText.text length])];
or
CGFloat topCorrect = displayText.contentSize.height -[displayText bounds].size.height;
topCorrect = (topCorrect<0.0?0.0:topCorrect);
displayText.contentOffset = (CGPoint){.x=0, .y=topCorrect};
But the former will now have the weird effect of starting at the top of a long length of text and animating the scroll to the bottom each time I append text to the view. Is there a way to pop down to the bottom of the text when I add text?
textView.scrollEnabled = NO;
[textView scrollRangeToVisible:NSMakeRange(textView.text.length - 1,0)];
textView.scrollEnabled = YES;
This really works for me in iOS 7.1.2.
For future travelers, building off of #mikeho's post, I found something that worked wonders for me, but is a bit simpler.
1) Be sure your UITextView's contentInsets are properly set & your textView is already firstResponder() before doing this.
2) After my the insets are ready to go, and the cursor is active, I call the following function:
private func scrollToCursorPosition() {
let caret = textView.caretRectForPosition(textView.selectedTextRange!.start)
let keyboardTopBorder = textView.bounds.size.height - keyboardHeight!
// Remember, the y-scale starts in the upper-left hand corner at "0", then gets
// larger as you go down the screen from top-to-bottom. Therefore, the caret.origin.y
// being larger than keyboardTopBorder indicates that the caret sits below the
// keyboardTopBorder, and the textView needs to scroll to the position.
if caret.origin.y > keyboardTopBorder {
textView.scrollRectToVisible(caret, animated: true)
}
}
I believe this is a bug in iOS 7. Toggling scrollEnabled on the UITextView seems to fix it:
[displayText scrollRangeToVisible:NSMakeRange(0,[displayText.text length])];
displayText.scrollEnabled = NO;
displayText.scrollEnabled = YES;
I think your parameters are reversed in NSMakeRange. Location is the first one, then how many you want to select (length).
NSMakeRange(0,[displayText.text length])
...would create a selection starting with the 0th (first?) character and going the entire length of the string. To scroll to the bottom you probably just want to select a single character at the end.
This is working for me in iOS SDK 7.1 with Xcdoe 5.1.1.
[textView scrollRangeToVisible:NSMakeRange(textView.text.length - 1,0)];
textView.scrollEnabled = NO;
textView.scrollEnabled = YES;
I do this as I add text programmatically, and the text views stays at the bottom like Terminal or command line output.
The best way is to set the bounds for the UITextView. It does not trigger scrolling and has an immediate effect of repositioning what is visible. You can do this by finding the location of the caret and then repositioning:
- (void)userInsertingNewText {
UITextView *textView;
// find out where the caret is located
CGRect caret = [textView caretRectForPosition:textView.selectedTextRange.start];
// there are insets that offset the text, so make sure we use that to determine the actual text height
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;
// only set the offset if the caret is out of view
if (textViewHeight < caret.origin.y) {
[self repositionScrollView:textView newOffset:CGPointMake(0, caret.origin.y - textViewHeight)];
}
}
/**
This method allows for changing of the content offset for a UIScrollView without triggering the scrollViewDidScroll: delegate method.
*/
- (void)repositionScrollView:(UIScrollView *)scrollView newOffset:(CGPoint)offset {
CGRect scrollBounds = scrollView.bounds;
scrollBounds.origin = offset;
scrollView.bounds = scrollBounds;
}

UILabel - auto-size label to fit text?

Is it possible to auto-resize the UILabel box/bounds to fit the contained text?
(I don't care if it ends up larger than the display)
So if a user enters "hello" or "my name is really long i want it to fit in this box", it is never truncated and the label is 'widened' accordingly?
Please check out my gist where I have made a category for UILabel for something very similar, my category lets a UILabel stretch it's height to show all the content: https://gist.github.com/1005520
Or check out this post: https://stackoverflow.com/a/7242981/662605
This would stretch the height, but you can change it around easily to work the other way and stretch the width with something like this, which is I believe what you want to do:
#implementation UILabel (dynamicSizeMeWidth)
- (void)resizeToStretch{
float width = [self expectedWidth];
CGRect newFrame = [self frame];
newFrame.size.width = width;
[self setFrame:newFrame];
}
- (float)expectedWidth{
[self setNumberOfLines:1];
CGSize maximumLabelSize = CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX);
CGSize expectedLabelSize = [[self text] sizeWithFont:[self font]
constrainedToSize:maximumLabelSize
lineBreakMode:[self lineBreakMode]];
return expectedLabelSize.width;
}
#end
You could more simply use the sizeToFit method available from the UIView class, but set the number of lines to 1 to be safe.
iOS 6 update
If you are using AutoLayout, then you have a built in solution. By setting the number of lines to 0, the framework will resize your label appropriately (adding more height) to fit your text.
iOS 8 update
sizeWithFont: is deprecated so use sizeWithAttributes: instead:
- (float)expectedWidth{
[self setNumberOfLines:1];
CGSize expectedLabelSize = [[self text] sizeWithAttributes:#{NSFontAttributeName:self.font}];
return expectedLabelSize.width;
}
Using [label sizeToFit]; will achieve the same result from Daniels Category.
Although I recommend to use autolayout and let the label resize itself based on constraints.
If we want that UILabel should shrink and expand based on text size then storyboard with autolayout is best option. Below are the steps to achieve this
Steps
Put UILabel in view controller and place it wherever you want. Also put 0 for numberOfLines property of UILabel.
Give it Top, Leading and Trailing space pin constraint.
Now it will give warning, Click on the yellow arrow.
Click on Update Frame and click on Fix Misplacement. Now this UILabel will shrink if text is less and expand if text is more.
This is not as complicated as some of the other answers make it.
Pin the left and top edges
Just use auto layout to add constraints to pin the left and top sides of the label.
After that it will automatically resize.
Notes
Don't add constraints for the width and height. Labels have an intrinsic size based on their text content.
Thanks to this answer for help with this.
No need to set sizeToFit when using auto layout. My complete code for the example project is here:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
#IBAction func changeTextButtonTapped(sender: UIButton) {
myLabel.text = "my name is really long i want it to fit in this box"
}
}
If you want your label to line wrap then set the number of lines to 0 in IB and add myLabel.preferredMaxLayoutWidth = 150 // or whatever in code. (I also pinned my button to the bottom of the label so that it would move down when the label height increased.)
If you are looking for dynamically sizing labels inside a UITableViewCell then see this answer.
Use [label sizeToFit]; to adjust the text in UILabel
Here's what I am finding works for my situation:
1) The height of the UILabel has a >= 0 constraint using autolayout. The width is fixed.
2) Assign the text into the UILabel, which already has a superview at that point (not sure how vital that is).
3) Then, do:
label.sizeToFit()
label.layoutIfNeeded()
The height of the label is now set appropriately.
I created some methods based Daniel's reply above.
-(CGFloat)heightForLabel:(UILabel *)label withText:(NSString *)text
{
CGSize maximumLabelSize = CGSizeMake(290, FLT_MAX);
CGSize expectedLabelSize = [text sizeWithFont:label.font
constrainedToSize:maximumLabelSize
lineBreakMode:label.lineBreakMode];
return expectedLabelSize.height;
}
-(void)resizeHeightToFitForLabel:(UILabel *)label
{
CGRect newFrame = label.frame;
newFrame.size.height = [self heightForLabel:label withText:label.text];
label.frame = newFrame;
}
-(void)resizeHeightToFitForLabel:(UILabel *)label withText:(NSString *)text
{
label.text = text;
[self resizeHeightToFitForLabel:label];
}
#implementation UILabel (UILabel_Auto)
- (void)adjustHeight {
if (self.text == nil) {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.bounds.size.width, 0);
return;
}
CGSize aSize = self.bounds.size;
CGSize tmpSize = CGRectInfinite.size;
tmpSize.width = aSize.width;
tmpSize = [self.text sizeWithFont:self.font constrainedToSize:tmpSize];
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, aSize.width, tmpSize.height);
}
#end
This is category method. You must set text first, than call this method to adjust UILabel's height.
You can size your label according to text and other related controls using two ways-
For iOS 7.0 and above
CGSize labelTextSize = [labelText boundingRectWithSize:CGSizeMake(labelsWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : labelFont
}
context:nil].size;
before iOS 7.0 this could be used to calculate label size
CGSize labelTextSize = [label.text sizeWithFont:label.font
constrainedToSize:CGSizeMake(label.frame.size.width, MAXFLOAT)
lineBreakMode:NSLineBreakByWordWrapping];
// reframe other controls based on labelTextHeight
CGFloat labelTextHeight = labelTextSize.height;
If you do not want to calculate the size of the label's text than you can use -sizeToFit on the instance of UILabel as-
[label setNumberOfLines:0]; // for multiline label
[label setText:#"label text to set"];
[label sizeToFit];// call this to fit size of the label according to text
// after this you can get the label frame to reframe other related controls
Add missing constraints in storyboard.
Select UILabel in storyboard and set the attributes "Line" to 0.
Ref Outlet the UILabel to Controller.h with id:label
Controller.m and add [label sizeToFit]; in viewDidLoad
the sizeToFit() method doesn't play well with constraints, but as of iOS 9 this is all you need -
label.widthAnchor.constraint(equalToConstant: label.intrinsicContentSize.width).activate()
I had a huge problems with auto layout.
We have two containers inside table cell. Second container is resized depending on Item description (0 - 1000 chars), and row should be resized based on them.
The missing ingredient was bottom constraint for description.
I've changed bottom constraint of dynamic element from = 0 to >= 0.
Fits everytime! :)
name.text = #"Hi this the text I want to fit to"
UIFont * font = 14.0f;
CGSize size = [name.text sizeWithAttributes:#{NSFontAttributeName: font}];
nameOfAssessment.frame = CGRectMake(400, 0, size.width, 44);
nameOfAssessment.font = [UIFont systemFontOfSize:font];
you can show one line output then set property Line=0 and show multiple line output then set property Line=1 and more
[self.yourLableName sizeToFit];
There's also this approach:
[self.myLabel changeTextWithAutoHeight:self.myStringToAssignToLabel width:180.0f];

Resources