Convert UIKeyboardFrameEndUserInfoKey to View or Window Coordinates
Asked Answered
L

3

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.

Lumpy answered 14/3, 2013 at 6:1 Comment(3)
Hope the answer below helps. I previously used convertRect as well, but the code below is cleaner, IMO.Conflict
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: apeth.com/iOSBook/…Passional
@Passional 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.Conflict
P
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.

Passional answered 22/3, 2013 at 3:5 Comment(6)
+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}?Conflict
Be aware that you need also to convert the rect to window coordinate system! The answer from @david-m-syzdek is more complete.Muscadel
@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.Passional
@Passional I didn't tested on iOS 8 so far, anyway to support older iOS versions you need to convert the rect.Muscadel
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 !!Caird
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.Quan
L
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];
Larynx answered 17/5, 2013 at 18:10 Comment(6)
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.Pascha
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?Hoggish
@Pascha I'm guessing the edge case is the iPad Pro with scaling? Oh wait, sorry, that's an anachronism.Rexferd
@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.Pascha
@Pascha 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)Rexferd
@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.Pascha
L
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];

    // https://mcmap.net/q/244006/-convert-uikeyboardframeenduserinfokey-to-view-or-window-coordinates
    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];
    }];
}
Leatherjacket answered 22/1, 2014 at 13:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.