ChatGPT解决这个技术问题 Extra ChatGPT

How do I size a UITextView to its content?

Is there a good way to adjust the size of a UITextView to conform to its content? Say for instance I have a UITextView that contains one line of text:

"Hello world"

I then add another line of text:

"Goodbye world"

Is there a good way in Cocoa Touch to get the rect that will hold all of the lines in the text view so that I can adjust the parent view accordingly?

As another example, look at the notes' field for events in the Calendar application - note how the cell (and the UITextView it contains) expands to hold all lines of text in the notes' string.

please consider updating correct answer, since the accepted one leads to unnecessary calculations, now there is a contentSize property for this issue.
why are there no questions about how to do this non-dynamically? I can't even find out how to do it staticly. And the provided solutions don't seem to override "constraints" in storyboard.

A
APerson

This works for both iOS 6.1 and iOS 7:

- (void)textViewDidChange:(UITextView *)textView
{
    CGFloat fixedWidth = textView.frame.size.width;
    CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
    CGRect newFrame = textView.frame;
    newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height);
    textView.frame = newFrame;
}

Or in Swift (Works with Swift 4.1 in iOS 11)

let fixedWidth = textView.frame.size.width
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
textView.frame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)

If you want support for iOS 6.1 then you should also:

textview.scrollEnabled = NO;

Tested on iOS 7 to resize an UITextView into an UITableViewCell. Works really great. This is the only answer that finally worked for me. Thanks!
I needed the textview.scrollEnabled = NO; to prevent the text from getting cut off even in iOS 7.
I recommend to use CGFLOAT_MAX macro instead of MAXFLOAT. Proper on both of 32/64 bit platforms.
The one who face little fluctuation before entering in next line add this ` NSRange bottom = NSMakeRange(textView.text.length -1, 1); [textView scrollRangeToVisible:bottom];`
Why are there two separate calls to sizeThatFits(...) in the Swift version? What's the first line for?
J
Jakub

This no longer works on iOS 7 or above

There is actually a very easy way to do resizing of the UITextView to its correct height of the content. It can be done using the UITextView contentSize.

CGRect frame = _textView.frame;
frame.size.height = _textView.contentSize.height;
_textView.frame = frame;

One thing to note is that the correct contentSize is only available after the UITextView has been added to the view with addSubview. Prior to that it is equal to frame.size

This will not work if auto layout is ON. With auto layout, the general approach is to use the sizeThatFits method and update the constant value on a height constraint.

CGSize sizeThatShouldFitTheContent = [_textView sizeThatFits:_textView.frame.size];
heightConstraint.constant = sizeThatShouldFitTheContent.height;

heightConstraint is a layout constraint that you typically setup via a IBOutlet by linking the property to the height constraint created in a storyboard.

Just to add to this amazing answer, 2014, if you:

[self.textView sizeToFit];

there is a difference in behaviour with the iPhone6+ only:

https://i.stack.imgur.com/GCC09.png

With the 6+ only (not the 5s or 6) it does add "one more blank line" to the UITextView. The "RL solution" fixes this perfectly:

CGRect _f = self.mainPostText.frame;
_f.size.height = self.mainPostText.contentSize.height;
self.mainPostText.frame = _f;

It fixes the "extra line" problem on 6+.


You saved my day. I was struggling with selfCalculating class function extension forgetting about this. Thank You.
Aha! I was setting text view height and then adjusting the height of the containing view. Apparently when I did that the text view height was being adjusted. Setting the containing view height first makes things work as expected (or, better, hoped for).
Using [_textView sizeToFit] refreshes the contentSize property, removing the requirement to add it to the scrollView beforehand.
If using autolayout, call layoutIfNeeded on the superview. Then you can grab the height from contentSize.
It's about resizing the outer container though. That behavior has changed in iOS 7. I show in the linked question where to add sizeToFit and layoutIfNeeded.
B
Brett Donald

Update

The key thing you need to do is turn off scrolling in your UITextView.

myTextView.scrollEnabled = @NO

Original Answer

To make a dynamically sizing UITextView inside a UITableViewCell, I found the following combination works in Xcode 6 with the iOS 8 SDK:

Add a UITextView to a UITableViewCell and constrain it to the sides

Set the UITextView's scrollEnabled property to NO. With scrolling enabled, the frame of the UITextView is independent of its content size, but with scrolling disabled, there is a relationship between the two.

If your table is using the original default row height of 44 then it will automatically calculate row heights, but if you changed the default row height to something else, you may need to manually switch on auto-calculation of row heights in viewDidLoad: tableView.estimatedRowHeight = 150; tableView.rowHeight = UITableViewAutomaticDimension;

For read-only dynamically sizing UITextViews, that’s it. If you’re allowing users to edit the text in your UITextView, you also need to:

Implement the textViewDidChange: method of the UITextViewDelegate protocol, and tell the tableView to repaint itself every time the text is edited: - (void)textViewDidChange:(UITextView *)textView; { [tableView beginUpdates]; [tableView endUpdates]; }

And don’t forget to set the UITextView delegate somewhere, either in Storyboard or in tableView:cellForRowAtIndexPath:


Best answer for me. Worked perfectly and UITableViewAutomaticDimension was added in iOS 5 so it is backwards-compatible.
Didn't quite work for me . The textViewDidChange method cause my tableview to bounce every time i typed a character. What I suggest is that instead of using that method , in the tableView : cellForRowAtIndexPath method, for the row with the textview on it, if you are in a read only mode set .scrollEnabled to NO, and if you are in an editing view set scroll enabled to YES. This way when just displaying it will always show the full text and when editing it will start with the full text and as you add further text will remain the same size with the earlier text scrolling off the top
furthermore I added a >= height constraint to the textview as an empty textfield had no height and couldn't be tapped on to start editing.
Excellent solution. For me, the tableView.rowHeight = UITableViewAutomaticDimension was unnecessary.
Thank you again Brett Donald! I forgot how to do this and found your answer once again over 1 year later 😀.
A
Alok

Very easy working solution using code and storyboard both.

By Code

textView.scrollEnabled = false

By Storyboard

Uncheck the Scrolling Enable

https://i.stack.imgur.com/CGWqW.png

No need to do anything apart of this.


This answer is correct. In StoryBoard you have the TextView AutoSize just like a UILabel. All we need to do is set scrollEnabled=false.
Essentially, this is correct: if you are using Autolayout constraints and shut off scrolling, the whole thing works.
Thanks a lot. It was that simple, just disable scrolling in storyboard or code, it will be like UILabel with lines 0.
@Mansuu.... This isn't a guaranteed solution, as there are things that you can do to override the intrinsic content size, and if you change the text after the view is laid out it won't update unless you set that up, too. But, those things remain true for setting numberOfLines = 0 on a textfield, too. If this doesn't work, you probably have other constraints that will implicitly set a height.
@iOS if you are adding height constraint then make constraints priority lower otherwise it will not work.
J
Juan Boero

Swift :

textView.sizeToFit()

In addition, I found that I needed to set scrolling enabled to false for this to work. textView.scrollEnabled = false
You should remember to call textView.layoutIfNeeded() after textView.sizeToFit()
@tmarkiewicz It works when the text fits on the screen of your device but it will be a problem when your text is bigger than the space that there is on the screen. You will not be able to scroll it to see the rest of the text.
This is the answer!!! No need for more complicated solutions!!! @FranciscoRomero Why not put the textview in a table cell or colleciton view cell then you can always scroll the table or colleciton view....
V
Venk

In my (limited) experience,

- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode

does not respect newline characters, so you can end up with a lot shorter CGSize than is actually required.

- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size

does seem to respect the newlines.

Also, the text isn't actually rendered at the top of the UITextView. In my code, I set the new height of the UITextView to be 24 pixels larger than the height returned by the sizeOfFont methods.


That gets the size as if it would draw the text on a UILabel. If you set that size to the uitextview's frame then you will still have some scrolling necessary in it.
S
Stian Høiland

In iOS6, you can check the contentSize property of UITextView right after you set the text. In iOS7, this will no longer work. If you want to restore this behavior for iOS7, place the following code in a subclass of UITextView.

- (void)setText:(NSString *)text
{
    [super setText:text];

    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
        CGRect rect = [self.textContainer.layoutManager usedRectForTextContainer:self.textContainer];
        UIEdgeInsets inset = self.textContainerInset;
        self.contentSize = UIEdgeInsetsInsetRect(rect, inset).size;
    }
}

Where can I read more about this? I'm totally lost as to the seemingly new behaviour in iOS 7 regarding this.
What's the meaning of the variable CGSize size?
I removed size, it was not supposed to be there.
N
Nikita Took

I will post right solution at the bottom of the page in case someone is brave (or despaired enough) to read to this point.

Here is gitHub repo for those, who don't want to read all that text: resizableTextView

This works with iOs7 (and I do believe it will work with iOs8) and with autolayout. You don't need magic numbers, disable layout and stuff like that. Short and elegant solution.

I think, that all constraint-related code should go to updateConstraints method. So, let's make our own ResizableTextView.

The first problem we meet here is that don't know real content size before viewDidLoad method. We can take long and buggy road and calculate it based on font size, line breaks, etc. But we need robust solution, so we'll do:

CGSize contentSize = [self sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];

So now we know real contentSize no matter where we are: before or after viewDidLoad. Now add height constraint on textView (via storyboard or code, no matter how). We'll adjust that value with our contentSize.height:

[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
    if (constraint.firstAttribute == NSLayoutAttributeHeight) {
        constraint.constant = contentSize.height;
        *stop = YES;
    }
}];

The last thing to do is to tell superclass to updateConstraints.

[super updateConstraints];

Now our class looks like:

ResizableTextView.m

- (void) updateConstraints {
    CGSize contentSize = [self sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];

    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            constraint.constant = contentSize.height;
            *stop = YES;
        }
    }];

    [super updateConstraints];
}

Pretty and clean, right? And you don't have to deal with that code in your controllers!

But wait! Y NO ANIMATION!

You can easily animate changes to make textView stretch smoothly. Here is an example:

    [self.view layoutIfNeeded];
    // do your own text change here.
    self.infoTextView.text = [NSString stringWithFormat:@"%@, %@", self.infoTextView.text, self.infoTextView.text];
    [self.infoTextView setNeedsUpdateConstraints];
    [self.infoTextView updateConstraintsIfNeeded];
    [UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{
        [self.view layoutIfNeeded];
    } completion:nil];

Use CGFLOAT_MAX, not FLT_MAX.
I wish I could give you more than one up vote for this awesome solution!!
M
Mike McMaster

Did you try [textView sizeThatFits:textView.bounds] ?

Edit: sizeThatFits returns the size but does not actually resize the component. I'm not sure if that's what you want, or if [textView sizeToFit] is more what you were looking for. In either case, I do not know if it will perfectly fit the content like you want, but it's the first thing to try.


sizetofit saves the day
V
Venk

Another method is the find the size a particular string will take up using the NSString method:

-(CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size

This returns the size of the rectangle that fits the given string with the given font. Pass in a size with the desired width and a maximum height, and then you can look at the height returned to fit the text. There is a version that lets you specify line break mode also.

You can then use the returned size to change the size of your view to fit.


K
Kishore Kumar

We can do it by constraints .

Set Height constraints for UITextView.

2.Create IBOutlet for that height constraint.

 @property (weak, nonatomic) IBOutlet NSLayoutConstraint *txtheightconstraints;

3.don't forget to set delegate for your textview.

4.

-(void)textViewDidChange:(UITextView *)textView
{
    CGFloat fixedWidth = textView.frame.size.width;
    CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
    CGRect newFrame = textView.frame;
    newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height);
    NSLog(@"this is updating height%@",NSStringFromCGSize(newFrame.size));
    [UIView animateWithDuration:0.2 animations:^{
                  _txtheightconstraints.constant=newFrame.size.height;
    }];

}

then update your constraint like this :)


that's a FANTASTIC nickname!
Setting the constant needs to be followed by [textView layoutIfNeeded]; in the animation block. (Autolayout animations do it like that.)
V
Venk

If you don't have the UITextView handy (for example, you're sizing table view cells), you'll have to calculate the size by measuring the string, then accounting for the 8 pt of padding on each side of a UITextView. For example, if you know the desired width of your text view and want to figure out the corresponding height:

NSString * string = ...;
CGFloat textViewWidth = ...;
UIFont * font = ...;

CGSize size = CGSizeMake(textViewWidth - 8 - 8, 100000);
size.height = [string sizeWithFont:font constrainedToSize:size].height + 8 + 8;

Here, each 8 is accounting for one of the four padded edges, and 100000 just serves as a very large maximum size.

In practice, you may want to add an extra font.leading to the height; this adds a blank line below your text, which may look better if there are visually heavy controls directly beneath the text view.


perfect result in iOS 8.1
Padding was the key for me
R
Roger

Starting with iOS 8, it is possible to use the auto layout features of a UITableView to automatically resize a UITextView with no custom code at all. I have put a project in github that demonstrates this in action, but here is the key:

The UITextView must have scrolling disabled, which you can do programmatically or through the interface builder. It will not resize if scrolling is enabled because scrolling lets you view the larger content. In viewDidLoad for the UITableViewController, you must set a value for estimatedRowHeight and then set the rowHeight to UITableViewAutomaticDimension.


- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableView.estimatedRowHeight = self.tableView.rowHeight;
    self.tableView.rowHeight = UITableViewAutomaticDimension;
}

The project deployment target must be iOS 8 or greater.


This kind of answers are discouraged. You should post the relevant code here.
P
Peter Stajger

I reviewed all the answers and all are keeping fixed width and adjust only height. If you wish to adjust also width you can very easily use this method:

so when configuring your text view, set scroll disabled

textView.isScrollEnabled = false

and then in delegate method func textViewDidChange(_ textView: UITextView) add this code:

func textViewDidChange(_ textView: UITextView) {
    let newSize = textView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
    textView.frame = CGRect(origin: textView.frame.origin, size: newSize)
}

Outputs:

https://i.stack.imgur.com/NFe0a.png

https://i.stack.imgur.com/k40zg.png


delegate method is not getting called @Peter Stajger
d
dheeraj

I found out a way to resize the height of a text field according to the text inside it and also arrange a label below it based on the height of the text field! Here is the code.

UITextView *_textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 10, 300, 10)];
NSString *str = @"This is a test text view to check the auto increment of height of a text view. This is only a test. The real data is something different.";
_textView.text = str;

[self.view addSubview:_textView];
CGRect frame = _textView.frame;
frame.size.height = _textView.contentSize.height;
_textView.frame = frame;

UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(10, 5 + frame.origin.y + frame.size.height, 300, 20)];
lbl.text = @"Hello!";
[self.view addSubview:lbl];

Here the '5' in y - coordinate of UILabel is the gap that I want between textView and Label.
S
Swaroop S

Guys using autolayout and your sizetofit isn't working, then please check your width constraint once. If you had missed the width constraint then the height will be accurate.

No need to use any other API. just one line would fix all the issue.

[_textView sizeToFit];

Here, I was only concerned with height, keeping the width fixed and had missed the width constraint of my TextView in storyboard.

And this was to show up the dynamic content from the services.

Hope this might help..


B
Bartłomiej Semańczyk

The following things are enough:

Just remember to set scrolling enabled to NO for your UITextView:

https://i.stack.imgur.com/Xuv3Z.png

Properly set Auto Layout Constraints.

You may even use UITableViewAutomaticDimension.


f
f0go

Using UITextViewDelegate is the easiest way:

func textViewDidChange(_ textView: UITextView) {
    textView.sizeToFit()
    textviewHeight.constant = textView.contentSize.height
}

O
Olie

Combined with Mike McMaster's answer, you might want to do something like:

[myTextView setDelegate: self];

...

- (void)textViewDidChange:(UITextView *)textView {
  if (myTextView == textView) {
     // it changed.  Do resizing here.
  }
}

A
Ali Ozkara

disable scrolling

https://i.stack.imgur.com/60exo.png

add constaints

https://i.stack.imgur.com/lH2fW.png

and add your text

[yourTextView setText:@"your text"];
[yourTextView layoutIfNeeded];

if you use UIScrollView you should add this too;

[yourScrollView layoutIfNeeded];

-(void)viewDidAppear:(BOOL)animated{
    CGRect contentRect = CGRectZero;

    for (UIView *view in self.yourScrollView.subviews) {
         contentRect = CGRectUnion(contentRect, view.frame);
    }
    self.yourScrollView.contentSize = contentRect.size;
}

V
Venk

This worked nicely when I needed to make text in a UITextView fit a specific area:

// The text must already be added to the subview, or contentviewsize will be wrong.

- (void) reduceFontToFit: (UITextView *)tv {
    UIFont *font = tv.font;
    double pointSize = font.pointSize;

    while (tv.contentSize.height > tv.frame.size.height && pointSize > 7.0) {
        pointSize -= 1.0;
        UIFont *newFont = [UIFont fontWithName:font.fontName size:pointSize];
        tv.font = newFont;
    }
    if (pointSize != font.pointSize)
        NSLog(@"font down to %.1f from %.1f", pointSize, tv.font.pointSize);
}

F
Fareed Alnamrouti

here is the swift version of @jhibberd

    let cell:MsgTableViewCell! = self.tableView.dequeueReusableCellWithIdentifier("MsgTableViewCell", forIndexPath: indexPath) as? MsgTableViewCell
    cell.msgText.text = self.items[indexPath.row]
    var fixedWidth:CGFloat = cell.msgText.frame.size.width
    var size:CGSize = CGSize(width: fixedWidth,height: CGFloat.max)
    var newSize:CGSize = cell.msgText.sizeThatFits(size)
    var newFrame:CGRect = cell.msgText.frame;
    newFrame.size = CGSizeMake(CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), newSize.height);
    cell.msgText.frame = newFrame
    cell.msgText.frame.size = newSize        
    return cell

The question is really language agnostic. Is it really necessary to go back and write swift ports for all answers on UIKit related questions? Seems like that makes for a noisy conversation.
the problem that most of these questions has no specific programming language so you will find it in google even if you search for swift language
C
Community

For iOS 7.0, instead of setting the frame.size.height to the contentSize.height (which currently does nothing) use [textView sizeToFit].

See this question.


a
atereshkov

This works fine for Swift 5 in case you want to fit your TextView once user write text on the fly.

Just implement UITextViewDelegate with:

func textViewDidChange(_ textView: UITextView) {
    let newSize = textView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
    textView.frame.size = CGSize(width: newSize.width, height: newSize.height)
}

A
Alejo JM

if any other get here, this solution work for me, 1"Ronnie Liew"+4"user63934" (My text arrive from web service): note the 1000 (nothing can be so big "in my case")

UIFont *fontNormal = [UIFont fontWithName:FONTNAME size:FONTSIZE];

NSString *dealDescription = [client objectForKey:@"description"];

//4
CGSize textSize = [dealDescription sizeWithFont:fontNormal constrainedToSize:CGSizeMake(containerUIView.frame.size.width, 1000)];

CGRect dealDescRect = CGRectMake(10, 300, containerUIView.frame.size.width, textSize.height);

UITextView *dealDesc = [[[UITextView alloc] initWithFrame:dealDescRect] autorelease];

dealDesc.text = dealDescription;
//add the subview to the container
[containerUIView addSubview:dealDesc];

//1) after adding the view
CGRect frame = dealDesc.frame;
frame.size.height = dealDesc.contentSize.height;
dealDesc.frame = frame;

And that is... Cheers


D
Dr. Marty

Hope this helps:

- (void)textViewDidChange:(UITextView *)textView {
  CGSize textSize = textview.contentSize;
  if (textSize != textView.frame.size)
      textView.frame.size = textSize;
}

This doesn't work! It causes an error: "lvalue required as left operand of assignment"
You can only assign to textView.frame.
M
Manju

The Best way which I found out to re-size the height of the UITextView according to the size of the text.

CGSize textViewSize = [YOURTEXTVIEW.text sizeWithFont:[UIFont fontWithName:@"SAMPLE_FONT" size:14.0]
                       constrainedToSize:CGSizeMake(YOURTEXTVIEW.frame.size.width, FLT_MAX)];

or You can USE

CGSize textViewSize = [YOURTEXTVIEW.text sizeWithFont:[UIFont fontWithName:@"SAMPLE_FONT" size:14.0]
                       constrainedToSize:CGSizeMake(YOURTEXTVIEW.frame.size.width, FLT_MAX) lineBreakMode:NSLineBreakByTruncatingTail];

G
Gal Blank

For those who want the textview to actually move up and maintain the bottom line position

CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;

if(frame.size.height > textView.frame.size.height){
    CGFloat diff = frame.size.height - textView.frame.size.height;
    textView.frame = CGRectMake(0, textView.frame.origin.y - diff, textView.frame.size.width, frame.size.height);
}
else if(frame.size.height < textView.frame.size.height){
    CGFloat diff = textView.frame.size.height - frame.size.height;
    textView.frame = CGRectMake(0, textView.frame.origin.y + diff, textView.frame.size.width, frame.size.height);
}

M
Mohammad Zekrallah

The only code that will work is the one that uses 'SizeToFit' as in jhibberd answer above but actually it won't pick up unless you call it in ViewDidAppear or wire it to UITextView text changed event.


A
Apfelsaft

Based on Nikita Took's answer I came to the following solution in Swift which works on iOS 8 with autolayout:

    descriptionTxt.scrollEnabled = false
    descriptionTxt.text = yourText

    var contentSize = descriptionTxt.sizeThatFits(CGSizeMake(descriptionTxt.frame.size.width, CGFloat.max))
    for c in descriptionTxt.constraints() {
        if c.isKindOfClass(NSLayoutConstraint) {
            var constraint = c as! NSLayoutConstraint
            if constraint.firstAttribute == NSLayoutAttribute.Height {
                constraint.constant = contentSize.height
                break
            }
        }
    }