How to re-size UITextView when keyboard shown with iOS 7

With Auto Layout, it's much easier (provided you understand Auto Layout) to handle:

Instead of trying to identify and resize the affected views, you simply create a parent frame for all your view's contents. Then, if the kbd appears, you resize the frame, and if you've set up the constraints properly, the view will re-arrange all its child views nicely. No need to fiddle with lots of hard-to-read code for this.

In fact, in a similar question I found a link to this excellent tutorial about this technique.

Also, the other examples here that do use textViewDidBeginEditing instead of the UIKeyboardWillShowNotification have one big issue:

If the user has an external bluetooth keyboard attached then the control would still get pushed up even though no on-screen keyboard appears. That's not good.

So, to summarize:

  1. Use Auto Layout
  2. Use the UIKeyboardWillShowNotification notification, not the TextEditField's events for deciding when to resize your views.

Alternatively, check out LeoNatan's reply. That might even be a cleaner and simpler solution (I've not tried myself yet).


I read the docs which talk about this very topic. I translated it into Swift and it worked absolutely beautifully for me.

This is used for a full page UITextView like iMessage.

I am using iOS 8.2 and Swift on XCode 6.2 and here's my code. Just call this setupKeyboardNotifications from your viewDidLoad or other initialization method.

func setupKeyboardNotifications() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWasShown:"), name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillBeHidden:"), name: UIKeyboardWillHideNotification, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as NSValue
    let kbSize = infoNSValue.CGRectValue().size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsetsZero
    codeTextView.contentInset = contentInsets
    codeTextView.scrollIndicatorInsets = contentInsets
}

Also if you are having issues with the caret being in the right place when rotated check for the orientation change and scroll to the right position.

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
    scrollToCaretInTextView(codeTextView, animated: true)
}

func scrollToCaretInTextView(textView:UITextView, animated:Bool) {
    var rect = textView.caretRectForPosition(textView.selectedTextRange?.end)
    rect.size.height += textView.textContainerInset.bottom
    textView.scrollRectToVisible(rect, animated: animated)
}

Swift 3:

func configureKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(aNotification:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(aNotification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func keyboardWasShown(aNotification:NSNotification) {
    let info = aNotification.userInfo
    let infoNSValue = info![UIKeyboardFrameBeginUserInfoKey] as! NSValue
    let kbSize = infoNSValue.cgRectValue.size
    let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0)
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}

func keyboardWillBeHidden(aNotification:NSNotification) {
    let contentInsets = UIEdgeInsets.zero
    textView.contentInset = contentInsets
    textView.scrollIndicatorInsets = contentInsets
}

Swift 4 & 5:

func setupKeyboardNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_ :)), name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}



@objc func keyboardWillShow(_ notification:NSNotification) {
    let d = notification.userInfo!
    var r = (d[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    r = self.textView.convert(r, from:nil)
    self.textView.contentInset.bottom = r.size.height
    self.textView.verticalScrollIndicatorInsets.bottom = r.size.height

}

@objc func keyboardWillHide(_ notification:NSNotification) {
    let contentInsets = UIEdgeInsets.zero
    self.textView.contentInset = contentInsets
    self.textView.verticalScrollIndicatorInsets = contentInsets
}

Do not resize the text view. Instead, set the contentInset and scrollIndicatorInsets bottom to the keyboard height.

See my answer here: https://stackoverflow.com/a/18585788/983912


Edit

I made the following changes to your sample project:

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    _caretVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(_scrollCaretToVisible) userInfo:nil repeats:YES];
}

- (void)_scrollCaretToVisible
{
    //This is where the cursor is at.
    CGRect caretRect = [self.textView caretRectForPosition:self.textView.selectedTextRange.end];

    if(CGRectEqualToRect(caretRect, _oldRect))
        return;

    _oldRect = caretRect;

    //This is the visible rect of the textview.
    CGRect visibleRect = self.textView.bounds;
    visibleRect.size.height -= (self.textView.contentInset.top + self.textView.contentInset.bottom);
    visibleRect.origin.y = self.textView.contentOffset.y;

    //We will scroll only if the caret falls outside of the visible rect.
    if(!CGRectContainsRect(visibleRect, caretRect))
    {
        CGPoint newOffset = self.textView.contentOffset;

        newOffset.y = MAX((caretRect.origin.y + caretRect.size.height) - visibleRect.size.height + 5, 0);

        [self.textView setContentOffset:newOffset animated:NO];
    }
}

Removed setting old caret position at first, as well as disabled animation. Now seems to work well.