How to resize UITextView on iOS when a keyboard appears?

24,271

Solution 1

Years gone, the question is still actual. Apple definitely should handle all these things itself. But it doesn't. Here's the new solution based on the official Apple's documentation plus bug fixes. It supports iOS 8, iOS 9, inputAccessoryView and is ready for new versions of iOS and new devices.

/* Apple's solution to resize keyboard but with accessory view support */

- (void)keyboardDidShow:(NSNotification*)aNotification {
    NSDictionary* info = [aNotification userInfo];
    CGRect keyboardFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    double keyboardHeight = [[UIScreen mainScreen] bounds].size.height - keyboardFrame.origin.y;

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardHeight, 0.0);
    editor.contentInset = contentInsets;
    editor.scrollIndicatorInsets = contentInsets;
}

- (void)keyboardWillHide:(NSNotification*)aNotification {
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    editor.contentInset = contentInsets;
    editor.scrollIndicatorInsets = contentInsets;

    // button to hide the keyboard
    buttonDone.enabled = false;
}

/* Fix issues with size classes and accessory view */

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    // fix incorrect size of the inputAccessoryView when size class changed
    // willTransitionToTraitCollection and traitCollectionDidChange can't help us
    if (editor && editor.inputAccessoryView && !editor.inputAccessoryView.hidden) {
        [editor resignFirstResponder];
    }
}

/* Hide accessory view if a hardware keyboard is present */

#define gThresholdForHardwareKeyboardToolbar 160.f // it's minimum height of the software keyboard on iPhone 4 in landscape mode

- (bool)isExternalKeyboard:(NSNotification*)aNotification {
    NSDictionary* info = [aNotification userInfo];
    CGRect keyboardFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    double keyboardHeight = [[UIScreen mainScreen] bounds].size.height - keyboardFrame.origin.y;

    return keyboardHeight < gThresholdForHardwareKeyboardToolbar;
}

- (void)keyboardWillShow:(NSNotification*)aNotification {
    if ([self isExternalKeyboard:aNotification]) {
        // hardware keyboard is present
        if (editor && editor.inputAccessoryView) {
            editor.inputAccessoryView.hidden = true;
        }
    } else {
        // only on-screen keyboard
        if (editor && editor.inputAccessoryView) {
            editor.inputAccessoryView.hidden = false;
        }
    }

    // button to hide the keyboard
    buttonDone.enabled = true;
}

Solution 2

The best result was reached by the following code. Also don't forget to set background color to UIView and place UITextView before other top-screen controls (e.g. UITabBar).

Editing of a text in the end still isn't perfect now. You may try to improve.

FirstViewController.h:

@interface FirstViewController : UIViewController {
    IBOutlet UIBarButtonItem *buttonDone;
    IBOutlet UITextView *textView;
    UITabBarController* tabBarController; // set from superview in AppDelegate (MainWindow.xib)
}

@property (nonatomic, retain) UITabBarController* tabBarController;

FirstViewController.m:

@synthesize tabBarController;

- (void)viewDidAppear:(BOOL)animated
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShown:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)moveTextViewForKeyboard:(NSNotification*)aNotification up:(BOOL)up {
    NSDictionary* userInfo = [aNotification userInfo];
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    CGRect keyboardEndFrame;

    [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
    [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
    [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:animationDuration];
    [UIView setAnimationCurve:animationCurve];

    CGRect newFrame = textView.frame;
    CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];
    keyboardFrame.size.height -= tabBarController.tabBar.frame.size.height;
    newFrame.size.height -= keyboardFrame.size.height * (up?1:-1);
    textView.frame = newFrame;

    [UIView commitAnimations];   
}

- (void)keyboardWillShown:(NSNotification*)aNotification
{
    buttonDone.enabled = true;
    [self moveTextViewForKeyboard:aNotification up:YES]; 
}

- (void)keyboardWillHide:(NSNotification*)aNotification
{
    buttonDone.enabled = false;
    [self moveTextViewForKeyboard:aNotification up:NO]; 
}

P.S. It's hard to code for iOS without stackoverflow...

Solution 3

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.

Solution 4

I ran into several issues trying to get my text view to scroll and animate correctly for both iOS 7 and iOS 8, and with the new QuickType feature. At first I was focused on animating the scroll view insets, but the behaviour differed between iOS 7 and 8 and could not get it working correctly for both.

Then I realized I can simplify things by just focusing on the frame and this worked for me with a lot more simple code. In summary:

  • register for the UIKeyboardDidChangeFrameNotification (this will notify when the QuickType is shown/hidden as well).
  • figure out how much vertical space you need to change your text view's frame by.
  • animate the frame size change.

Here is some code that illustrates the above:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidChangeFrameWithNotification:) name:UIKeyboardDidChangeFrameNotification object:nil];
}

- (void)keyboardDidChangeFrameWithNotification:(NSNotification *)notification {
    CGFloat keyboardVerticalIncrease = [self keyboardVerticalIncreaseForNotification:notification];
    [self animateTextViewFrameForVerticalOffset:keyboardVerticalIncrease];
}

- (CGFloat)keyboardVerticalIncreaseForNotification:(NSNotification *)notification {
    CGFloat keyboardBeginY = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].origin.y;
    CGFloat keyboardEndY = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].origin.y;
    CGFloat keyboardVerticalIncrease = keyboardBeginY - keyboardEndY;
    return keyboardVerticalIncrease;
}

- (void)animateTextViewFrameForVerticalOffset:(CGFloat)offset {
    CGFloat constant = self.bottomConstraint.constant;
    CGFloat newConstant = constant + offset;
    self.bottomConstraint.constant = newConstant;
    [self.view layoutIfNeeded];
    [UIView animateWithDuration:0.5 animations:^{
        [self.view layoutIfNeeded];
    }];
}

A quick note on the animation. I used Autolayout so I chose to animate the text view's NSAutoLayoutConstraint, not the frame directly. And to do this, I call [self.view layoutIfNeeded] before and inside the animation block. This is the correct way of animating constraints. I found this tip here.

Solution 5

It's worth noting that the upvoted answer only works if the device is in portrait mode (and not upside down), in other modes the bounds go wrong. I believe that you could sort this by using bounds to fix, but I couldn't get that to work so the below adjustment worked for me:

- (void)moveTextViewForKeyboard:(NSNotification*)aNotification up:(BOOL)up {


NSDictionary* userInfo = [aNotification userInfo];
NSTimeInterval animationDuration;
UIViewAnimationCurve animationCurve;
CGRect keyboardEndFrame;

[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];


[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];

CGRect newFrame = self.view.frame;

if (keyboardEndFrame.size.height >keyboardEndFrame.size.width)
{   //we must be in landscape
    if (keyboardEndFrame.origin.x==0)
    {   //upside down so need to flip origin
        newFrame.origin = CGPointMake(keyboardEndFrame.size.width, 0);
    }

    newFrame.size.width -= keyboardEndFrame.size.width * (up?1:-1);

} else
{   //in portrait
    if (keyboardEndFrame.origin.y==0)
    {
        //upside down so need to flip origin
        newFrame.origin = CGPointMake(0, keyboardEndFrame.size.height);
    }
    newFrame.size.height -= keyboardEndFrame.size.height * (up?1:-1);

}
self.view.frame = newFrame;

[UIView commitAnimations];



}
Share:
24,271
Dmitry
Author by

Dmitry

Updated on February 14, 2020

Comments

  • Dmitry
    Dmitry about 4 years

    There's UITextView inserted into tab in UITabBarController (on the iPhone).

    1. Fill UITextView with a lot of lines.
    2. Show a keyboard to edit text.

    What's happen? The keyboard hide a half of UITextView with cursor. Can't edit the text as the result.

    How to resolve the issue for all Apple mobile devices (with different screen resolution)? Thanks a lot for help!

  • Dmitry
    Dmitry over 12 years
    But how to resize UITextView to correct size on iPhone/iPad for example?
  • Dmitry
    Dmitry over 12 years
    But what about iPad, iPhone 4 and future new devices? This code will not work on all of them!
  • Matt S.
    Matt S. over 12 years
    As a follow up, I have looked at Apple's documentation. UIKeyboardWillShow/Hide is NOT deprecated. The code I showed will work on every device, you may just need to modify the coords for the different devices (just a simple UIDevice if statement)
  • Aaron Douglas
    Aaron Douglas over 12 years
    Try having a Japanese keyboard enabled - they have a typeahead that makes their keyboards taller. Pulling the dimensions off the keyboard and doing an animated curve is really the proper way to do it.
  • Ashley Mills
    Ashley Mills about 12 years
    Hardcoding coordinates is never the way to go.
  • SimplyKiwi
    SimplyKiwi about 12 years
    I know this is a bit unrelated and old but how would we do something similar but with UITextField's in custom UITableViewCell's?
  • ColinE
    ColinE over 10 years
    I am having problems getting this solution to work in iOS 7 - see the following: stackoverflow.com/questions/18968735/…
  • Josue Espinosa
    Josue Espinosa almost 9 years
    Very simple approach, I like it!
  • Dmitry
    Dmitry about 8 years
    The answer is not actual any more. Look to the new one.
  • shs
    shs almost 7 years
    This is the answer for iOS 10 too. Note the year of the other answers. I'm sure they worked on old OSs, but this one works on iOS 10 perfectly for me.