16

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!

Venk
  • 5,949
  • 9
  • 41
  • 52
Dmitry
  • 14,306
  • 23
  • 105
  • 189

10 Answers10

33

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...

Venk
  • 5,949
  • 9
  • 41
  • 52
Dmitry
  • 14,306
  • 23
  • 105
  • 189
  • I know this is a bit unrelated and old but how would we do something similar but with UITextField's in custom UITableViewCell's? – SimplyKiwi Mar 04 '12 at 20:45
  • 1
    I am having problems getting this solution to work in iOS 7 - see the following: http://stackoverflow.com/questions/18968735/how-to-re-size-uitextview-when-keyboard-shown-with-ios-7 – ColinE Sep 23 '13 at 20:58
  • 1
    The answer is not actual any more. Look to the new one. – Dmitry Feb 13 '16 at 22:04
9

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.

Community
  • 1
  • 1
Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
3

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.

Community
  • 1
  • 1
abc123
  • 8,043
  • 7
  • 49
  • 80
2

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];



}
2

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;
}
Dmitry
  • 14,306
  • 23
  • 105
  • 189
  • 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. – shs Jun 13 '17 at 05:23
1
- (void)registerKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(keyboardWillShow:)
     name:UIKeyboardDidShowNotification
     object:nil];

    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(keyboardWillHide:)
     name:UIKeyboardWillHideNotification
     object:nil];
}

- (void)unregisterKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

-(void) keyboardWillHide:(NSNotification *)note
{
   //adjust frame
}

-(void) keyboardWillShow:(NSNotification *)note
{
   //adjust frame 
}

and unregister Notification too in the dealloc

- (void)unregisterKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
Aswathy Bose
  • 4,279
  • 4
  • 32
  • 44
1

First add a few keyboard methods to the NSNotificationCenter defaultCenter

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) 
                                             name:UIKeyboardWillShowNotification object:self.view.window]; 

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) 
                                             name:UIKeyboardWillHideNotification object:self.view.window]; 

then you can change the sizes:

- (void)keyboardWillShow:(NSNotification *)notif
{
[thetextView setFrame:CGRectMake(20, 49, 280, 187)]; //Or where ever you want the view to go


}

- (void)keyboardWillHide:(NSNotification *)notif
{
[thetextView setFrame:CGRectMake(20, 49, 280, 324)]; //return it to its original position

}
Venk
  • 5,949
  • 9
  • 41
  • 52
Matt S.
  • 13,305
  • 15
  • 73
  • 129
  • But what about iPad, iPhone 4 and future new devices? This code will not work on all of them! – Dmitry Aug 24 '11 at 09:27
  • 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) – Matt S. Aug 24 '11 at 14:45
  • 3
    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. – Aaron Douglas Feb 01 '12 at 14:50
  • 10
    Hardcoding coordinates is never the way to go. – Ashley Mills Mar 02 '12 at 09:47
0

As a follow-up, the technique where you update the frame when the keyboard notification occurs does not work for iOS 7. For an alternative solution, see the following:

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

Community
  • 1
  • 1
ColinE
  • 68,894
  • 15
  • 164
  • 232
0

I tried the best answer here however I found a problem in it. If you have another text field on the same page, you click the text field, show the keyboard. You will notice the text view shrinks. However, if you click the text view now, you will notice the text view size shrinks again while it should not.

My solution to this problem is to maintain a property in view controller representing keyboard state(shown/hide). If the keyboard is current visible, the text view should not be shrunk. In case you are using keyboards of different sizes for different text inputs, you should also maintain the old keyboard size.

Be aware that this solution also didn't take different orientation into account, which may affect the way you calculate the size of text view.

@implementation MyViewController {
    BOOL keyboardShown;
    NSInteger keyboardHeight;
}

- (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.textView.frame;
    CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];

    NSInteger oldHeight = self->keyboardShown ? self->keyboardHeight : 0;
    NSInteger newHeight = up ? keyboardFrame.size.height : 0;
    NSInteger change = oldHeight - newHeight;

    self->keyboardShown = up;
    self->keyboardHeight = keyboardFrame.size.height;

    newFrame.size.height += change;
    self.textView.frame = newFrame;

    [UIView commitAnimations];
}
Harper
  • 1,794
  • 14
  • 31
0

In short, register the keyboard notification and do your re-sizing work when you are notified.

Di Wu
  • 6,436
  • 3
  • 35
  • 51