Auto growing UITextView Using Auto Layout

Hi everyone,

A few weeks ago we decided to use iOS7 as an excuse to move Cal to using Auto layout. I initially intended to transform only small parts of the app, but auto layout is just so easy and intuitive that I found myself changing the entire app to support auto layout. And after I finished, I decided to change some 3rd party libraries as well 🙂

I’ve been using HPGrowingTextView for a while now, and it’s a really nice library, but it’s very complicated and has more than a few positioning bugs, so I decided that it’s a great candidate to be replaced by simple auto layout constraints.

Here’s how you implement an Auto Growing Text View with auto layouts:

Step 1 – Find the height constraint

When using Auto layouts, you don’t manually change the frame of views, you change the constraints. So in order to dynamically change the height of the textView, we have to have a reference to it’s height constraint.
Even if you did not specify a height constraint in IB, if your UITextView is placed inside a view that is using auto layouts, than IB will auto generate constraints to position that view.
We will get a reference to the height constraint (either the implicit or explicit one) like that:

-(id) initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

-(void) awakeFromNib
{
    [self commonInit];    
}

-(void) commonInit
{
    // If we are using auto layouts, than get a handler to the height constraint.
    for (NSLayoutConstraint *constraint in self.constraints) {
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            self.heightConstraint = constraint;
            break;
        }
    }    
}

Step 2 – Dynamically calculate the height change

To dynamically change the height, we have to get modified when ever the height of the inner text changes. Luckily, layoutSubview will be called every time the inner text changes in such a way that might require scrolling – which is exactly what we want.
Basically, the new height is simply be the contentSize, however, iOS7 added the option to set textContainerInset so we have to take them into account:

- (void) layoutSubviews
{
    [super layoutSubviews];
    
    CGSize intrinsicSize = self.intrinsicContentSize;
    self.heightConstraint.constant = intrinsicSize.height;    
}

- (CGSize)intrinsicContentSize
{
    CGSize intrinsicContentSize = self.contentSize;
    
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
        intrinsicContentSize.width += (self.textContainerInset.left + self.textContainerInset.right ) / 2.0f;
        intrinsicContentSize.height += (self.textContainerInset.top + self.textContainerInset.bottom) / 2.0f;
    }
    
    return intrinsicContentSize;
}

Step 3 – Support Max and min heights

In Cal, we had to set maximum height for the textView. There are two ways to set max and min heights. If you are building your constraints in IB, you can simply add an additional height constraint, set it’s type to “Less than or equal” and make sure that it’s priority is higher than the original height constraint. You can do the same for a min height constraint by setting the type to “Greater than or equal”. You can do this since auto layout allow “competing” constraints provided that they have different priorities.
A simpler way is just to pass the minHeight and maxHeight values by code and make sure that we don’t change the height past those values:

- (void) layoutSubviews
{
    [super layoutSubviews];
    
    CGSize intrinsicSize = self.intrinsicContentSize;
    
    if (self.minHeight) {
        intrinsicSize.height = MAX(intrinsicSize.height, self.minHeight);
    }
    if (self.maxHeight) {
        intrinsicSize.height = MIN(intrinsicSize.height, self.maxHeight);
    }
    
    self.heightConstraint.constant = intrinsicSize.height;    
}

That’s it. A super simple implementation of an auto growing textView using auto layouts.
As usual, I uploaded the code to Github as a drop in component (I also added some additional features like vertical alignment and non-auto-layout support).
Enjoy

Advertisements

5 Responses to Auto growing UITextView Using Auto Layout

  1. Flemming says:

    Looks very interesting – unfortunately the link to Github is not working for me.

  2. Adam Groom says:

    Thanks for that, very useful 🙂

  3. Ges says:

    You should probably call [super awakeFromNib]

  4. Fábio says:

    Hi, Adam… is there any way to make set vertical align to TOP (remove the top space)?

    Regards,
    Fabio.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: