28

I have a UITextView added on my UIView. The textview added is not editable, it is just to display some data. The data displayed in the textview is dynamic. Thats is the number of lines is not fixed. It may vary. So if the number of line increases, the size of the textview also needs to be increased. I have no clue how to do this. Please give me some ideas.

UPDATE:

Here's what I'm doing:

UIView *baseView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 200)];
baseView.backgroundColor = [UIColor grayColor];
[window addSubview:baseView];

UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(5, 30, 100, 30)];
textView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
textView.text = @"asdf askjalskjalksjlakjslkasj";
[textView sizeToFit];
[baseView addSubview:textView];
bobobobo
  • 64,917
  • 62
  • 258
  • 363
saikamesh
  • 4,569
  • 11
  • 53
  • 93
  • possible duplicate of [How do I size a UITextView to its content?](http://stackoverflow.com/questions/50467/how-do-i-size-a-uitextview-to-its-content) – Dave Haigh Sep 07 '15 at 17:43

10 Answers10

58

There is an answer posted at How do I size a UITextView to its content?

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

or better(taking into account contentInset thanks to kpower's comment)

CGRect frame = _textView.frame;
UIEdgeInsets inset = textView.contentInset;
frame.size.height = _textView.contentSize.height + inset.top + inset.bottom;
_textView.frame = frame;

note: If you are going to reference a property of an object many times(e.g. frame or contentInset) it's better to assign it to a local variable so you don't trigger extra method calls(_textView.frame/[_textView frame] are method calls). If you are calling this code a lot(100000s of times) then this will be noticeably slower(a dozen or so method calls is insignificant).

However... if you want to do this in one line without extra variables it would be

_textView.frame = CGRectMake(_textView.frame.origin.x, _textView.frame.origin.y, _textView.frame.size.width, _textView.contentSize.height + _textView.contentInset.top + _textView.contentInset.bottom);

at the expense of 5 extra method calls.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Gabe
  • 2,279
  • 1
  • 23
  • 22
30

You can use setFrame: or sizeToFit.

UPDATE:

I use sizeToFit with UILabel, and it works just fine, but UITextView is a subclass of UIScrollView, so I can understand why sizeToFit doesn't produce the desired result.

You can still calculate the text height and use setFrame, but you might want to take advantage of UITextView's scrollbars if the text is too long.

Here's how you get the text height:

#define MAX_HEIGHT 2000

NSString *foo = @"Lorem ipsum dolor sit amet.";
CGSize size = [foo sizeWithFont:[UIFont systemFontOfSize:14]
              constrainedToSize:CGSizeMake(100, MAX_HEIGHT)
                  lineBreakMode:UILineBreakModeWordWrap];

and then you can use this with your UITextView:

[textView setFont:[UIFont systemFontOfSize:14]];
[textView setFrame:CGRectMake(5, 30, 100, size.height + 10)];

or you can do the height calculation first and avoid the setFrame line:

UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(5, 30, 100, size.height + 10)];
Can Berk Güder
  • 109,922
  • 25
  • 130
  • 137
  • actually I don't know how to delete this answer and how to update my own question. There is no option for doing these. (FYI im using safari browser) – saikamesh Apr 08 '09 at 11:11
  • sorry! just now I saw the options for those – saikamesh Apr 08 '09 at 11:12
  • this works fine. but it needs the frame to be set initially. that is before ur piece of code I added textView = [[UITextView alloc] initWithFrame:CGRectMake(5.0, 30.0, 100, 30)];. – saikamesh Apr 08 '09 at 13:06
  • after adding the above only the textview becomes visible. and the last line in the text view is not displayed fully. only the upper half of it gets displayed. – saikamesh Apr 08 '09 at 13:08
  • any clue to solve this issue (only the upper half of last line gets displayed) – saikamesh Apr 08 '09 at 14:06
  • That's because the UITextView itself is larger than the text. Just replace size.height with size.height + 10 or something. – Can Berk Güder Apr 08 '09 at 16:14
  • solution you gave works fine. thanks a lot. but please tell me why UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(5.0, 30.0, 100, 30)] needs to be given at first – saikamesh Apr 08 '09 at 17:02
  • Well, you have to initialize textView to something. But you can do the height calculation first and getRid of the setFrame line. – Can Berk Güder Apr 08 '09 at 17:12
  • 3
    the correct answer is below, by Gabe. It doesn't rely on a magical +10 and is more accurate. – TomSwift Nov 10 '10 at 19:50
4

sizeToFit Does Work

If you call sizeToFit after you set the text the first time it resizes. So after the first time you set it subsequent calls to set text will result in no change in size. Even if you call sizeToFit.

However, you can force it to resize like this:

  1. Set the text.
  2. Change the textView frame height to be CGFLOAT_MAX.
  3. Call sizeToFit.
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
3

textView.contentSize.height in the textViewDidChange can only resize after text actually grows. For best visual result better to resize beforehand. After several hours I've figured out how to make it the same perfectly as in Instagram (it has the best algorithm among all BTW)

Initialize with this:

    // Input
    _inputBackgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, size.height - _InputBarHeight, size.width, _InputBarHeight)];
    _inputBackgroundView.autoresizingMask = UIViewAutoresizingNone;
   _inputBackgroundView.contentMode = UIViewContentModeScaleToFill;
    _inputBackgroundView.userInteractionEnabled = YES;
    [self addSubview:_inputBackgroundView];
   [_inputBackgroundView release];

   [_inputBackgroundView setImage:[[UIImage imageNamed:@"Footer_BG.png"] stretchableImageWithLeftCapWidth:80 topCapHeight:25]];

    // Text field
    _textField = [[UITextView alloc] initWithFrame:CGRectMake(70.0f, 0, 185, 0)];
   _textField.backgroundColor = [UIColor clearColor];
    _textField.delegate = self;
   _textField.contentInset = UIEdgeInsetsMake(-4, -2, -4, 0);
   _textField.showsVerticalScrollIndicator = NO;
   _textField.showsHorizontalScrollIndicator = NO;
    _textField.font = [UIFont systemFontOfSize:15.0f];
    [_inputBackgroundView addSubview:_textField];
   [_textField release];

   [self adjustTextInputHeightForText:@""];

Fill UITextView delegate methods:

- (void) textViewDidBeginEditing:(UITextView*)textView {

   [self adjustTextInputHeightForText:_textField.text];
}

- (void) textViewDidEndEditing:(UITextView*)textView {

   [self adjustTextInputHeightForText:_textField.text];
}

- (BOOL) textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {

   if ([text isEqualToString:@"\n"])
   {
      [self performSelector:@selector(inputComplete:) withObject:nil afterDelay:.1];
      return NO;
   }
   else if (text.length > 0)
   {
      [self adjustTextInputHeightForText:[NSString stringWithFormat:@"%@%@", _textField.text, text]];
   }
   return YES;
}

- (void) textViewDidChange:(UITextView*)textView {

   [self adjustTextInputHeightForText:_textField.text];
}

And the trick is...

- (void) adjustTextInputHeightForText:(NSString*)text {

   int h1 = [text sizeWithFont:_textField.font].height;
   int h2 = [text sizeWithFont:_textField.font constrainedToSize:CGSizeMake(_textField.frame.size.width - 16, 170.0f) lineBreakMode:UILineBreakModeWordWrap].height;

   [UIView animateWithDuration:.1f animations:^
   {
      if (h2 == h1)
      {
         _inputBackgroundView.frame = CGRectMake(0.0f, self.frame.size.height - _InputBarHeight, self.frame.size.width, _InputBarHeight);
      }
      else
      {
         CGSize size = CGSizeMake(_textField.frame.size.width, h2 + 24);
         _inputBackgroundView.frame = CGRectMake(0.0f, self.frame.size.height - size.height, self.frame.size.width, size.height);
      }
      CGRect r = _textField.frame;
      r.origin.y = 12;
      r.size.height = _inputBackgroundView.frame.size.height - 18;
      _textField.frame = r;

   } completion:^(BOOL finished)
   {
      //
   }];
}
DD_
  • 7,230
  • 11
  • 38
  • 59
m8labs
  • 3,671
  • 2
  • 30
  • 32
  • Doesn't grow the field when return is pressed. Autocorrect prompts are shown below the keyboard. Doesn't handle landscape. – Kirby Todd Jan 25 '13 at 15:18
2

This works perfectly for me:

#define MAX_HEIGHT 2000     
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:14]
      constrainedToSize:CGSizeMake(100, MAX_HEIGHT)
          lineBreakMode:UILineBreakModeWordWrap];

[textview setFont:[UIFont systemFontOfSize:14]];
[textview setFrame:CGRectMake(45, 6, 100, size.height + 10)];
textview.text = text;
pkamb
  • 33,281
  • 23
  • 160
  • 191
Viggnesh
  • 247
  • 2
  • 5
1

Do the following:

_textView.text = someText;
[_textView sizeToFit];
_textView.frame.height = _textView.contentSize.height;
shanet
  • 7,246
  • 3
  • 34
  • 46
Dat Tran
  • 21
  • 2
1

Addressing the similar issue I just created a an auto-layout based light-weight UITextView subclass which automatically grows and shrinks based on the size of user input and can be constrained by maximal and minimal height - all without a single line of code.

https://github.com/MatejBalantic/MBAutoGrowingTextView

Matej Balantič
  • 1,627
  • 18
  • 21
1

The answer given by @Gabe doesn't work in iOS7.1 seemingly until after viewDidAppear. See my tests below.

UPDATE: Actually, the situation is even more complicated. If you assign textView.text in the resizeTheTextView method, in iOS7, the resizing amounts to allowing for only a single line of text. Seriously odd.

UPDATE2: See also UITextView content size different in iOS7

UPDATE3: See my code at the very bottom for what I'm using now. Seems to do the job.

#import "ViewController.h"

@interface ViewController ()
{
    UITextView *textView;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    textView = [[UITextView alloc] initWithFrame:CGRectMake(50, 50, 200, 1)];
    [self.view addSubview:textView];
    CALayer *layer = textView.layer;
    layer.borderColor = [UIColor blackColor].CGColor;
    layer.borderWidth = 1;

    textView.text = @"hello world\n\n";

    // Calling the method directly, after the view is rendered, i.e., after viewDidAppear, works on both iOS6.1 and iOS7.1
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button setTitle:@"Change size" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(resizeTheTextView) forControlEvents:UIControlEventTouchUpInside];
    [button sizeToFit];
    CGRect frame = button.frame;
    frame.origin.y = 400;
    button.frame = frame;
    [self.view addSubview:button];

    // Works on iOS6.1, but does not work on iOS7.1
    //[self resizeTheTextView];
}

- (void) viewWillAppear:(BOOL)animated
{
    // Does not work on iOS7.1, but does work on iOS6.1
    //[self resizeTheTextView];
}

- (void) viewDidAppear:(BOOL)animated
{
    // Does work on iOS6.1 and iOS7.1
    //[self resizeTheTextView];
}

- (void) resizeTheTextView
{
    NSLog(@"textView.frame.size.height: %f", textView.frame.size.height);

    NSLog(@"textView.contentSize.height: %f", textView.contentSize.height);

    // 5) From https://stackoverflow.com/questions/728704/resizing-uitextview
    CGRect frame = textView.frame;
    UIEdgeInsets inset = textView.contentInset;
    frame.size.height = textView.contentSize.height + inset.top + inset.bottom;
    textView.frame = frame;

    NSLog(@"inset.top: %f, inset.bottom: %f",  inset.top,  inset.bottom);

    NSLog(@"textView.frame.size.height: %f", textView.frame.size.height);

    NSLog(@"textView.contentSize.height: %f", textView.contentSize.height);
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

UPDATE3:

if ([[UIDevice currentDevice] majorVersionNumber] < 7.0) {
    CGRect frame = _abstractTextView.frame;
    UIEdgeInsets inset = _abstractTextView.contentInset;
    frame.size.height = _abstractTextView.contentSize.height + inset.top + inset.bottom;
    _abstractTextView.frame = frame;
}
else {
    CGSize textViewSize = [_abstractTextView sizeThatFits:CGSizeMake(_abstractTextView.frame.size.width, FLT_MAX)];
    _abstractTextView.frameHeight = textViewSize.height;
}
Community
  • 1
  • 1
Chris Prince
  • 7,288
  • 2
  • 48
  • 66
1

After you add the UITextView to its parent if you set a Content Mode on it then it seems to resize itself automatically.

This means you don't need to work out the height manually and apply a height contraint. It just seems to work!! Tested in iOS7 and iOS8 on iPad.

e.g.

--
textView.contentMode = UIViewContentMode.Center;
--

If anyone can explain why this works it would be much appreciated.. I found it by accident when messing with options in interface builder.

Dave Haigh
  • 4,369
  • 5
  • 34
  • 56
  • @Martijn Pieters I've voted this question as the duplicate. You should undelete the answer from the other question and delete this one if those are the rules. – Dave Haigh Sep 07 '15 at 17:46
1

Just set scrollEnabled to NO, or uncheck Scrolling Enabled in the Scroll View section in IB and the UITextView will self-size.

Mojo66
  • 1,109
  • 12
  • 21