51

I'm working on an iPad app using 3.2 sdk. I'm dealing with obtaining the keyboard size to prevent my textfields from hidding behind it.

I'm getting a Warning in Xcode -> UIKeyboardBoundsUserInfoKey is deprecated what should I use instead not to get this warning?

Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
Mikeware
  • 867
  • 2
  • 8
  • 12

9 Answers9

87

I played with the previously offered solution but still had issues. Here's what I came up with instead:

    - (void)keyboardWillShow:(NSNotification *)aNotification {
    [self moveTextViewForKeyboard:aNotification up:YES];
}

    - (void)keyboardWillHide:(NSNotification *)aNotification {
        [self moveTextViewForKeyboard:aNotification up:NO]; 
    }

- (void) moveTextViewForKeyboard:(NSNotification*)aNotification up: (BOOL) up{
NSDictionary* userInfo = [aNotification userInfo];

// Get animation info from userInfo
NSTimeInterval animationDuration;
UIViewAnimationCurve animationCurve;

CGRect keyboardEndFrame;

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


[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];


// Animate up or down
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];

CGRect newFrame = textView.frame;
CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];

newFrame.origin.y -= keyboardFrame.size.height * (up? 1 : -1);
textView.frame = newFrame;

[UIView commitAnimations];
}
Burak
  • 5,706
  • 20
  • 70
  • 110
Jay
  • 4,480
  • 3
  • 25
  • 22
  • 8
    While this code will generally work, there are some breaking conditions. For example, if the user has more than one keyboard installed and they switch keyboards using the international keyboard key (which only displays if you have more than one keyboard installed). This will throw a UIKeyboardWillShowNotification, even though the keyboard is already shown. The result will be that your textField will be moved up another keyboard height up the screen. This will occur every time the international keyboard key is pressed. – memmons Aug 19 '10 at 18:10
  • 1
    I agree with Harkonian that you will need to set a BOOL that keeps track of whether there is already a keyboard being displayed. Before UIKeyboardBoundsUserInfoKey, I had offered a solution that's very similar to Jason's solution with the addition of the BOOL that kept track of the presence of the keyboard. I do like Jason's solution though. Very clean. http://stackoverflow.com/questions/1126726/how-to-make-a-uitextfield-move-up-when-keyboard-is-present/2703756#2703756 – Shiun Sep 15 '10 at 21:18
  • @Answerbot: My fix to that Chinese/Japanese keyboard problem below: http://stackoverflow.com/a/12953447/401329 – junjie Oct 18 '12 at 11:22
  • What about using animation blocks instead? – Rivera Apr 12 '16 at 13:09
57

From the documentation for UIKeyboardBoundsUserInfoKey:

The key for an NSValue object containing a CGRect that identifies the bounds rectangle of the keyboard in window coordinates. This value is sufficient for obtaining the size of the keyboard. If you want to get the origin of the keyboard on the screen (before or after animation) use the values obtained from the user info dictionary through the UIKeyboardCenterBeginUserInfoKey or UIKeyboardCenterEndUserInfoKey constants. Use the UIKeyboardFrameBeginUserInfoKey or UIKeyboardFrameEndUserInfoKey key instead.

Apple recommends implementing a convenience routine such as this (which could be implemented as a category addition to UIScreen):

+ (CGRect) convertRect:(CGRect)rect toView:(UIView *)view {
    UIWindow *window = [view isKindOfClass:[UIWindow class]] ? (UIWindow *) view : [view window];
    return [view convertRect:[window convertRect:rect fromWindow:nil] fromView:nil];
}

to recover window-adjusted keyboard frame size properties.

I took a different approach, which involves checking the device orientation:

CGRect _keyboardEndFrame;
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&_keyboardEndFrame];
CGFloat _keyboardHeight = ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait || [[UIDevice currentDevice] orientation] == UIDeviceOrientationPortraitUpsideDown) ? _keyboardEndFrame.size.height : _keyboardEndFrame.size.width;
Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345
  • Thank you Alex. I was very hard to find information on this topic, your response will surely help in my project. – Mikeware May 11 '10 at 16:49
  • Do you happen to have a link to that recommendation by Apple? – Senseful May 19 '10 at 05:21
  • I got that recommendation through a response to a bug report ticket. I don't know where you will find that information publicly, though. – Alex Reynolds May 19 '10 at 05:38
  • 1
    This technique will fail if the screen orientation doesn't match the device orientation, so it shouldn't be considered a general purpose solution. – Micah Hainline Sep 21 '11 at 20:14
  • I had some orientation issues with this as well. I'm now simply taking the minimum of the two "CGFloat _keyboardHeight = MIN(_keyboardEndFrame.size.height, _keyboardEndFrame.size.width);", which works fine in my case. – Johan Pelgrim Dec 01 '11 at 22:36
  • what orientation issue did u have ? – poojathorat Feb 03 '15 at 09:20
9

You simply use this code:

//NSVale *aValue = [info objectForKey:UIKeyboardBoundsUserInfoKey];
//instead of Upper line we can use either next line or nextest line.
//NSValue *aValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
NSValue *aValue = [info objectForKey:UIKeyboardFrameBeginUserInfoKey];
sth
  • 222,467
  • 53
  • 283
  • 367
iOS_User
  • 1,372
  • 5
  • 21
  • 35
3

The following code fixes an issue in Jay's answer, which assumes that UIKeyboardWillShowNotification will not fire again when the keyboard is already present.

When typing with the Japanese/Chinese keyboard, iOS fires an extra UIKeyboardWillShowNotification with the new keyboard frame even though the keyboard is already present, leading to the height of the self.textView being reduced a second time in the original code.

This reduces self.textView to almost nothing. It then becomes impossible to recover from this problem since we will only expect a single UIKeyboardWillHideNotification the next time the keyboard is dismissed.

Instead of subtracting/adding height to self.textView depending on whether the keyboard is shown/hidden as in the original code, the following code just calculates the maximum possible height for self.textView after subtracting the height of the keyboard on screen.

This assumes that self.textView is suppose to fill the entire view of the view controller, and there's no other subview that needs to be visible.

- (void)resizeTextViewWithKeyboardNotification:(NSNotification*)notif {

    NSDictionary* userInfo = [notif userInfo];
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    CGRect keyboardFrameInWindowsCoordinates;

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

    [self resizeTextViewToAccommodateKeyboardFrame:keyboardFrameInWindowsCoordinates
                             withAnimationDuration:animationDuration
                                    animationCurve:animationCurve];

}

- (void)resizeTextViewToAccommodateKeyboardFrame:(CGRect)keyboardFrameInWindowsCoordinates
                           withAnimationDuration:(NSTimeInterval)duration
                                  animationCurve:(UIViewAnimationCurve)curve
{

    CGRect fullFrame = self.view.frame;

    CGRect keyboardFrameInViewCoordinates =
    [self.view convertRect:keyboardFrameInWindowsCoordinates fromView:nil];

    // Frame of the keyboard that intersects with the view. When keyboard is
    // dismissed, the keyboard frame still has width/height, although the origin
    // keeps the keyboard out of the screen.
    CGRect keyboardFrameVisibleOnScreen =
    CGRectIntersection(fullFrame, keyboardFrameInViewCoordinates);

    // Max frame availble for text view. Assign it to the full frame first
    CGRect newTextViewFrame = fullFrame;

    // Deduct the the height of any keyboard that's visible on screen from
    // the height of the text view
    newTextViewFrame.size.height -= keyboardFrameVisibleOnScreen.size.height;

    if (duration)
    {
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:duration];
        [UIView setAnimationCurve:curve];
    }

    // Adjust the size of the text view to the new one
    self.textView.frame = newTextViewFrame;

    if (duration)
    {
        [UIView commitAnimations];
    }

}

Also, don't forget to register the keyboard notifications in viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSNotificationCenter* notifCenter = [NSNotificationCenter defaultCenter];

    [notifCenter addObserver:self selector:@selector(resizeTextViewWithKeyboardNotification:) name:UIKeyboardWillShowNotification object:nil];
    [notifCenter addObserver:self selector:@selector(resizeTextViewWithKeyboardNotification:) name:UIKeyboardWillHideNotification object:nil];
}

About splitting the resizing code into two parts

The reason why the textView resizing code is split into two parts (resizeTextViewWithKeyboardNotification: and resizeViewToAccommodateKeyboardFrame:withAnimationDuration:animationCurve:) is to fix another issue when the keyboard persists through a push from one view controller to another (see How do I detect the iOS keyboard when it stays up between controllers?).

Since the keyboard is already present before the view controller is pushed, there's no additional keyboard notifications being generated by iOS, and thus no way to resize the textView based on those keyboard notifications.

The above code (as well as the original code) that resizes self.textView will thus only work when the keyboard is shown after the view has been loaded.

My solution is to create a singleton that stores the last keyboard coordinates, and on - viewDidAppear: of the viewController, call:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Resize the view if there's any keyboard presence before this
    // Only call in viewDidAppear as we are unable to convertRect properly
    // before view is shown
    [self resizeViewToAccommodateKeyboardFrame:[[UASKeyboard sharedKeyboard] keyboardFrame]
                         withAnimationDuration:0
                                animationCurve:0];
}

UASKeyboard is my singleton here. Ideally we should call this in - viewWillAppear:, however in my experience (at least on iOS 6), the convertRect:fromView: method that we need to use in resizeViewToAccommodateKeyboardFrame:withAnimationDuration:animationCurve: does not properly convert the keyboard frame to the view coordinates before the view is fully visible.

Community
  • 1
  • 1
junjie
  • 7,946
  • 2
  • 26
  • 26
  • This is great: very few developers know about the Japanese/Chinese keyboards and do the extra work to account for them. – cbowns Jul 26 '13 at 18:29
  • @junjie I have a problem when this is in landscape as it reduces the width rather than the height, but other than that seems to work. – Recycled Steel Jan 28 '14 at 15:21
2

Just use the UIKeyboardFrameBeginUserInfoKey or UIKeyboardFrameEndUserInfoKey key instead of UIKeyboardBoundsUserInfoKey

BBog
  • 3,630
  • 5
  • 33
  • 64
Anand Mishra
  • 1,093
  • 12
  • 15
1

@Jason, you code if fine except for one point.

At the moment you are not actually animating anything and the view will simply `pop' to its new size.height.

You have to specify a state from which to animate. An animation is a sort of (from state)->(to state) thing.

Luckily there is a very convenient method to specify the current state of the view as the (from state).

[UIView setAnimationBeginsFromCurrentState:YES];

If you add that line right after beginAnimations:context: your code works perfectly.

Thomas
  • 11
  • 1
  • See the documentation on setAnimationsBeginsFromCurrentState: "This method does nothing if an animation is not in flight or invoked outside of an animation block. The default value is NO." So unless you are doing something outside the normal scope of this animation, you don't need to add it to the code. Every time I've used this it animates just fine – Jay Dec 27 '10 at 21:38
1
- (CGSize)keyboardSize:(NSNotification *)aNotification {
    NSDictionary *info = [aNotification userInfo];
    NSValue *beginValue = [info objectForKey:UIKeyboardFrameBeginUserInfoKey];

    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

    CGSize keyboardSize;
    if ([UIKeyboardDidShowNotification isEqualToString:[aNotification name]]) {
        _screenOrientation = orientation;
        if (UIDeviceOrientationIsPortrait(orientation)) {
            keyboardSize = [beginValue CGRectValue].size;
        } else {
            keyboardSize.height = [beginValue CGRectValue].size.width;
            keyboardSize.width = [beginValue CGRectValue].size.height;
        }
    } else if ([UIKeyboardDidHideNotification isEqualToString:[aNotification name]]) {
        // We didn't rotate
        if (_screenOrientation == orientation) {
            if (UIDeviceOrientationIsPortrait(orientation)) {
                keyboardSize = [beginValue CGRectValue].size;
            } else {
                keyboardSize.height = [beginValue CGRectValue].size.width;
                keyboardSize.width = [beginValue CGRectValue].size.height;
            }
        // We rotated
        } else if (UIDeviceOrientationIsPortrait(orientation)) {
            keyboardSize.height = [beginValue CGRectValue].size.width;
            keyboardSize.width = [beginValue CGRectValue].size.height;
        } else {
            keyboardSize = [beginValue CGRectValue].size;
        }
    }


    return keyboardSize;
}
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126
0

Its worked like this

This is the constraint of the save button bottom

@IBOutlet weak var saveBtnBottom: NSLayoutConstraint!
@IBOutlet weak var nameText: UITextField!

Inside viewDidLoad

NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
nameText.delegate = self

This is the functions we need

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    nameText.resignFirstResponder()
    return true
}


@objc func keyBoardWillShow(notification: Notification){
    if let userInfo = notification.userInfo as? Dictionary<String, AnyObject>{
        let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey]
        let keyBoardRect = frame?.cgRectValue
        if let keyBoardHeight = keyBoardRect?.height {
            self.saveBtnBottom.constant = keyBoardHeight 

            UIView.animate(withDuration: 0.5, animations: {
                self.view.layoutIfNeeded()
            })
        }
    }
}

@objc func keyBoardWillHide(notification: Notification){
    self.saveBtnBottom.constant = 30.0
    UIView.animate(withDuration: 0.5, animations: {
        self.view.layoutIfNeeded()
    })
}
0

Here is a good details http://i-phone-dev.blogspot.com/2012/01/different-way-to-show-keyboard-and.html

karim
  • 15,408
  • 7
  • 58
  • 96