28

For the constant UIKeyboardFrameEndUserInfoKey, in the Apple docs it says:

These coordinates do not take into account any rotation factors applied to the window’s contents as a result of interface orientation changes. Thus, you may need to convert the rectangle to window coordinates (using the convertRect:fromWindow: method) or to view coordinates (using the convertRect:fromView: method) before using it.

So if I use [view1 convertRect:rect fromView:view2]

What would I insert for the above parameters to get it to convert the rotation values correctly? ie:

view1 = ? rect = ? (the keyboard frame I'm assuming) view2 = ?

Been trying some things and getting some funny stuff.

Ser Pounce
  • 14,196
  • 18
  • 84
  • 169
  • Hope the answer below helps. I previously used `convertRect` as well, but the code below is cleaner, IMO. – memmons Mar 22 '13 at 03:07
  • Obviously I don't agree with @Answerbot as to what's "cleaner" :) BTW the code I show comes from my book, which you might like to consult on this matter: http://www.apeth.com/iOSBook/ch23.html#_summoning_and_dismissing_the_keyboard – matt Mar 22 '13 at 03:09
  • @matt Good stuff. I've read one of your books and really enjoyed it. The reason I stopped using using `convertRect` is because it only really works well inside a view controller where you have a `myView` which represents the topmost view. If however, you are listening for notifications from within a `UITextField` subclass, the transformation using self isn't particularly helpful. – memmons Mar 22 '13 at 04:35

3 Answers3

69

The first view should be your view. The second view should be nil, meaning window/screen coordinates. Thus:

NSDictionary* d = [notification userInfo];
CGRect r = [d[UIKeyboardFrameEndUserInfoKey] CGRectValue];
r = [myView convertRect:r fromView:nil];

Now you have the rect that the keyboard will occupy, in terms of your view. If your view is the current view controller's view (or a subview thereof), rotation and so forth are now accounted for.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 4
    +1, but I think this is only useful if `myView` represents the root view of a view controller. What if it doesn't though and instead myView is a subview like `{100,100,200,200}`? – memmons Mar 22 '13 at 16:57
  • 1
    Be aware that you need also to convert the rect to window coordinate system! The answer from @david-m-syzdek is more complete. – Diogo T Apr 29 '14 at 21:04
  • 1
    @DiogoTridapalli Just to be clear, in iOS 8 there is no need to convert through the window coordinate system to handle the case of landscape orientation, because the entire app rotates and the keyboard has rotated with it. – matt Sep 19 '14 at 16:17
  • @matt I didn't tested on iOS 8 so far, anyway to support older iOS versions you need to convert the rect. – Diogo T Sep 19 '14 at 18:19
  • 1
    Brilliant tip. Frustratingly, I had an app in the App Store which hadn't been using the ConvertRect function. It all worked fine before iOS 8 came along. Now, with iOS 8, suddenly my landscape iPad app was reporting the keyboard was 1024 pixels high, and messing up my screen. So, even though this tip is a year and a half old, now, it's more important than ever !! – Mike Gledhill Nov 26 '14 at 08:35
  • It appears that if you want the height of the keyboard, your best bet is not to use the height of the frame, but rather the screen's height minus the frame's origin's y-coordinate. The keyboard has a nonzero height even when it is dismissing, but its frame's origin will be the bottom edge of the screen. – BallpointBen Feb 02 '18 at 06:27
23

I tried the accepted answer and found that it does not actually provide the CGRect of the keyboard within the view. I found that I have to convert the CGRect from the UIScreen object to the UIWindow object, and from the UIWindow object to the UIView object:

NSValue * keyboardEndFrame;
CGRect    screenRect;
CGRect    windowRect;
CGRect    viewRect;

// determine's keyboard height
screenRect    = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
windowRect    = [self.view.window convertRect:screenRect fromWindow:nil];
viewRect      = [self.view        convertRect:windowRect fromView:nil];

I use the above to resize the root view to not be hidden by the keyboard:

NSTimeInterval  duration;
CGRect          frame;

// determine length of animation
duration  = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

// resize the view
frame              = self.view.frame;
frame.size.height -= viewRect.size.height;

// animate view resize with the keyboard movement
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:duration];
self.view.frame = frame;
[UIView commitAnimations];
David M. Syzdek
  • 15,360
  • 6
  • 30
  • 40
  • I ran into a case where I had to do this as well. So, I'm going to do it this way from now on. – ma11hew28 Oct 30 '13 at 18:32
  • I'm using this method, but getting obviously incorrect `origin` values of the viewRect for landscape orientations (negative `y`). In your solution, you use only the `height`. Does your `origin` contain correct `y` value? – burax Oct 02 '15 at 22:09
  • @ma11hew28 I'm guessing the edge case is the iPad Pro with scaling? Oh wait, sorry, that's an anachronism. – Dan Rosenstark Jan 22 '19 at 01:40
  • @DanRosenstark, sorry. I don't remember. And now, I probably do things differently. For example, I get `keyboardHeightEnd` from `UIResponder.keyboardWillShowNotification`, like so: `let rect = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey]! as! NSValue).cgRectValue; let keyboardHeightEnd = view.convert(rect, from: nil).size.height`. – ma11hew28 Jan 22 '19 at 22:21
  • @ma11hew28 that looks like a slightly different language ;) But yes, all good. I just wonder if the scaling factors are picked up properly for all devices including the aspect-ratio-weird ones (11" iPad Pro) – Dan Rosenstark Jan 22 '19 at 23:42
  • @DanRosenstark, yeah, I use Swift now. I don't know. I didn't test it, but I imagine the code in my last comment (specifically the `UIView` instance method `convert(_:from:)`) works properly for all devices/scales. If it didn't, I feel like that'd be a UIKit bug. – ma11hew28 Jan 24 '19 at 00:38
1
+ (void)parseKeyboardNotification:(NSNotification *)notification
                 inRelationToView:(UIView *)view
                             info:(void(^)(NSTimeInterval keyboardAnimationDuration, CGRect keyboardFrameInView, UIViewAnimationOptions keyboardAnimationOptions))callback
{
    NSParameterAssert(notification != nil);
    NSParameterAssert(view != nil);

    NSDictionary *userInfo = [notification userInfo];

    UIViewAnimationCurve animationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
    UIViewAnimationOptions animationOption = animationCurve << 16; // https://devforums.apple.com/message/878410#878410
    NSTimeInterval animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];

    // http://stackoverflow.com/a/16615391/202451
    CGRect screenRect    = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect windowRect    = [view.window convertRect:screenRect fromWindow:nil];
    CGRect viewRect      = [view        convertRect:windowRect fromView:nil];

    callback(animationDuration, viewRect, animationOption);
}

Can be used like this

- (void)keyboardWillShowOrHide:(NSNotification *)notification
{    
    [AGKeyboardInfo parseKeyboardNotification:notification inRelationToView:self.view info:^(NSTimeInterval keyboardAnimationDuration, CGRect keyboardFrameInView, UIViewAnimationOptions keyboardAnimationOptions) {

        [UIView animateWithDuration:keyboardAnimationDuration delay:0 options:keyboardAnimationOptions animations:^{

             // do any modifications to your views

        } completion:nil];
    }];
}
hfossli
  • 22,616
  • 10
  • 116
  • 130