How to resize a UITextView dynamically with Auto Layout

Resizing a UITextView (or a UIScrollView) dynamically on the fly isn’t easy – or so it seems at first. Should we move or squish the UIView? Should we create a new frame for the enclosing view and animate that? But no, those things worked fine in the days before Auto Layout. Nowadays we have the excellent UIStackView in which our text view may be embedded – so what are we to do?

Turns out the easiest way would be to change one of the constraints we have given our outermost stack view programmatically.

In my example I have a stack view that fills the screen, and inside it I have a text view for the user to read text, and a text field underneath it for text input. When the keyboard comes up, I’d like the text field to slide up and the text view to get smaller, and as soon as the user taps return I’d like everything to go back to where it was. Oh, and I’d like for this to work in portrait and landscape, and on all iPad sizes as well. Like in the animation above. Without going insane.

Here’s how we do that.

The trick is to grab a reference to the bottom constraint in Interface Builder so we can change it in code. I had never done this before, but it’s just as easy as creating a reference to any other element in our scene. All constraints are grouped together in the Scene Tab on the left.

Hover over each constraint and take a look at which one lights up in the layout, then CTRL-drag into your code using the Assistant Editor. There’s your reference.

When the keyboard comes up…

Now for the clever bit: as soon as the keyboard comes up, we detect its height and add that to the contraint’s constant property. This will increase the amount of space between the outer frame and the stack view. Because the text view is squish able, and the text field is not, we can leave the difficult calculations up to iOS and the stack view.

Here’s what this looks like in code:

- (void)keyboardCameUp:(NSNotification *)notification {
    
    // make the textfield visible when the keyboard shows
    
    // stash original constraint
    self.resetConstant = self.bottomContraint.constant;
    
    // add height to bottom constraint
    CGFloat keyboardHeight = [self grabKeyboardHeight:notification];
    self.bottomContraint.constant = keyboardHeight +10;
    
    // and animate the whole thing
    NSTimeInterval duration = [self grabAnimationDuration:notification];
    [UIView animateWithDuration:duration animations:^{
        [self.view layoutIfNeeded];
    }];
    
    [self positionTextView];
}

Here’s what’s happening: First we grab a reference to the current constraint so we can bring it back. We’ll stash this value in a float. Note that we’re not stashing the full NSLayoutConstraint here, for some reason it won’t work when we try to bring it back. But the constant is all we need, so we’ll stash that.

Next we’ll figure out the current height of the keyboard. We’ll do that via the NSNotification’s info dictionary. We’ll add 10 pixels to it just to create a nicer looking gap between the keyboard and the text field. We’ll also grab the duration of how long it takes for the keyboard to be animated on the screen so we can match it to our own animation (it’ll look as if it’s a native process then). I’ve explained how to do this here.

We call layoutIfNeeded inside the animation block on our view, which will take care of repositioning the constraint and recalculating the positions inside the stack view.

Lastly, we’ll make sure that the text inside the text view remains at the bottom. I’ve explained how to do this here.

When the keyboard goes away…

When the keyboard is removed from the screen again, we’ll do the same steps in reverse order: again detect the height of the keyboard, bring back our stashed layout constraint constant and expand the stack view again:

- (void)keyboardWentAway:(NSNotification *)notification {
    
    NSLog(@"Keyboard went away!");
    
    // maximise the view when keyboard is gone
    
    // reset bottom constraint
    self.bottomContraint.constant = self.resetConstant;
    
    // and animate the whole thing
    NSTimeInterval duration = [self grabAnimationDuration:notification];
    [UIView animateWithDuration:duration animations:^{
        [self.view layoutIfNeeded];
    }];
    
    [self positionTextView];
}

You’ll find the code for detecting the keyboardHeight and aninmationDuration in my two previous posts.

Thanks to Think in G for explaining this principle in a post from 2012.





Leave a Reply