43

In Custom Keyboard Extension , we can't use

`didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation` 

and sharedApplication.

I need to detect portrait or landscape in keyboard when rotate.

How can i detect when orientation change in Custom Keyboard extension?

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
Fire Fist
  • 7,032
  • 12
  • 63
  • 109
  • I encourage you to change your accepted answer. As the current solution does not work for iPads. – Albert Renshaw Aug 01 '14 at 05:05
  • Why? For iPad you can change view frame size width to 768 for Portrait. For Landscape you can do it with if else. – Fire Fist Aug 01 '14 at 05:13
  • Does not support future device sizes though. You would have to update your app. I believe I read something about Apple coming out with two new iPhone screen sizes. http://www.macrumors.com/roundup/iphone-6/ – Albert Renshaw Aug 01 '14 at 05:14
  • Yes. When they release devices with new size , we can detect View.frame.size.width or height for each devices. – Fire Fist Aug 01 '14 at 05:16
  • 1
    But you would have to update your app just for this reason? Why not have it all detected from the start? Plus then you end up with multiple conditional statements instead of just one. – Albert Renshaw Aug 01 '14 at 05:17
  • The other problem with the currently accepted solution is that `viewDidLayoutSubviews` gets called twice upon an orientation change. We only want the method to be called ONCE per orientation change. And with the current method, the second time it is called (which is the time that the proper orientation is finally detectable), the orientation animation has already completed and your keyboard stretch will appear laggy. – Albert Renshaw Aug 01 '14 at 05:26
  • Added best solution working on all screen sizes and using strictly non-deprecated methods here: http://stackoverflow.com/a/25222353/2057171 – Albert Renshaw Aug 09 '14 at 19:50
  • I once more urge you to change your accepted answer, the currently accepted answer will not work on iPhone6 and iPhone6+ and anyone who used that code will have to update their apps because of this. – Albert Renshaw Sep 15 '14 at 19:02

10 Answers10

43

In order to update your custom keyboard when the orientation changes, override viewDidLayoutSubviews in the UIInputViewController. As far as I can tell, when a rotation occurs this method is always called.

Additionally, as the traditional [UIApplication sharedApplication] statusBarOrientation] doesn't work, to determine the current orientation use the following snippet:

if([UIScreen mainScreen].bounds.size.width < [UIScreen mainScreen].bounds.size.height){
    //Keyboard is in Portrait
}
else{
    //Keyboard is in Landscape
}

Hopefully this helps!

Guntis Treulands
  • 4,764
  • 2
  • 50
  • 72
Matt
  • 1,359
  • 18
  • 22
26

Non-deprecated and will work on any device screen size (including future screen sizes Apple will be releasing this year).

In CustomKeyboard ViewController.m:

-(void)viewDidLayoutSubviews {
    NSLog(@"%@", (self.view.frame.size.width == ([[UIScreen mainScreen] bounds].size.width*([[UIScreen mainScreen] bounds].size.width<[[UIScreen mainScreen] bounds].size.height))+([[UIScreen mainScreen] bounds].size.height*([[UIScreen mainScreen] bounds].size.width>[[UIScreen mainScreen] bounds].size.height))) ? @"Portrait" : @"Landscape");
}

done.


Or... for a more easy to read version of this code:

-(void)viewDidLayoutSubviews {
    
    int appExtensionWidth = (int)round(self.view.frame.size.width);
    
    int possibleScreenWidthValue1 = (int)round([[UIScreen mainScreen] bounds].size.width);
    int possibleScreenWidthValue2 = (int)round([[UIScreen mainScreen] bounds].size.height);
    
    int screenWidthValue;
    
    if (possibleScreenWidthValue1 < possibleScreenWidthValue2) {
        screenWidthValue = possibleScreenWidthValue1;
    } else {
        screenWidthValue = possibleScreenWidthValue2;
    }
    
    if (appExtensionWidth == screenWidthValue) {
        NSLog(@"portrait");
    } else {
        NSLog(@"landscape");
    }
}
greybeard
  • 2,249
  • 8
  • 30
  • 66
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
  • 1
    On iPhone 6 simulator, it prints out `Landscape` even though the device is in portrait mode. – Salavat Khanov Sep 16 '14 at 15:26
  • @SalavatKhanov Works fine for me in iPhone6 simulator. Which xCode BETA are you running? – Albert Renshaw Sep 17 '14 at 03:05
  • This breaks down with apps not built for iphone 6 and phone 6+ dimensions. For instance, I'm testing whatsapp now and it reports the self.view.frame to be (0, 0, 320, 216) and the screen size is (414, 736) so it throws this logic off. – yuklai Nov 13 '14 at 20:02
  • @fatshu The logic still works just fine? It's just the numbers are off haha – Albert Renshaw Nov 13 '14 at 22:05
  • I have the same but how to get device is landScapeLeft or landScapeRight like this one please if possible then check this and help me http://stackoverflow.com/questions/25462091/get-device-current-orientation-app-extension – Anand Suthar Nov 14 '14 at 11:48
  • oh dear this is so hacky, apple need to get on their damn game – Shai UI Dec 24 '14 at 05:16
  • @foreyez Not hacky :) It is very easy to follow logic. Assuming new screen sizes that come out will always have the longer side being the vertical side (If they dont then they are in landscape by default so this code still works). If height is shortest is becomes the "horizontal side" (shortest side in portrait), if width is shortest it becomes the "horizontal side" (shortest side in portrait). We get the width of the app extension (keyboards always span the width of the screen (portrait). If the horizontal side is equal to keyboard width its portrait, else landscape. – Albert Renshaw Dec 24 '14 at 17:24
  • that might be true, but I noticed viewDidLayoutSubviews gets called multiple times, after you change to landscape (from portrait). AND it gives multiple heights for the new widths (both heights given are smaller than the new landscape width) so I don't know which height to use. I don't know why it's so hard for apple to just give us some callback that happens ONCE and says what the old width/height was, what the new width/height is, and the orientation. it's not rocket science. you have thousands of developers working on creating things, why can't they provide the most basic information. – Shai UI Dec 24 '14 at 18:01
  • @foreyez Hahahaha Yeah I don't know what they were thinking on that one. But the multiple sizes returned is why the logic code above is used :D – Albert Renshaw Dec 25 '14 at 03:59
4

There is a simple way, just looking at the screen width:

 double width = [[UIScreen mainScreen] bounds].size.width;
 double interfaceWidth = MIN([[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height);

 BOOL isPortrait = (width == interfaceWidth) ? YES : NO;
  • 1
    Wouldn't it be simpler to just see if width < height? e.g.: `CGSize screenSize = [UIScreen mainScreen].bounds.size; BOOL isPortrait = screenSize.width < screenSize.height;` – progrmr Oct 09 '14 at 13:01
3

use viewWillTransitionToSize:(CGSize)size withTransitionCoordinator: inside your viewController

taffarel
  • 4,015
  • 4
  • 33
  • 61
  • 3
    The method doesn’t seem to be called when the controller is running in the keyboard extension mode. Doesn’t work for me on the Simulator nor the actual device, as of iOS 8 Beta 5. Does it work for you? I’d love it to work, it looks like the correct, future-proof solution. – zoul Aug 13 '14 at 15:31
  • To be honest I didn't test it before posting, You are right, it doesn't work, as a alternate way you can use viewDidLayoutSubviews which called a little bit later, but hope in future it will work as it works for normal UIViewControllers. – taffarel Aug 14 '14 at 15:31
  • 1
    My Radar issue was marked as a duplicate of 17472132, which is currently still open. Hopefully that means this solution will eventually work. – zoul Oct 30 '14 at 07:54
  • 1
    As of iOS 8.3 beta 2, `viewWillTransitionToSize` is finally getting called during orientation changes. On the other hand the old workaround in `viewDidLayoutSubviews` stopped working for me, since the reported view size is wrong. Let’s hope we won’t have to handle the rotation according to specific iOS versions… (The Radar issue is still open.) – zoul Mar 06 '15 at 11:25
  • 1
    I believe this is now the accepted method to be using. It is working in my app and i'm using it to trigger re-layout of a programmatic text view when the orientation changes. – Natalia Nov 20 '15 at 01:01
1
- (void)updateViewConstraints {
    [super updateViewConstraints];

    // Add custom view sizing constraints here
    if (self.view.frame.size.width == 0 || self.view.frame.size.height == 0)
        return;

    [self.inputView removeConstraint:self.heightConstraint];
    CGSize screenSize = [[UIScreen mainScreen] bounds].size;
    CGFloat screenH = screenSize.height;
    CGFloat screenW = screenSize.width;
    BOOL isLandscape =  !(self.view.frame.size.width ==
                      (screenW*(screenW<screenH))+(screenH*(screenW>screenH)));
    NSLog(isLandscape ? @"Screen: Landscape" : @"Screen: Potriaint");
    self.isLandscape = isLandscape;
    if (isLandscape) {
        self.heightConstraint.constant = self.landscapeHeight;
        [self.inputView addConstraint:self.heightConstraint];
    } else {
        self.heightConstraint.constant = self.portraitHeight;
        [self.inputView addConstraint:self.heightConstraint];
    }

    //trigger default first view
    [btn_gif sendActionsForControlEvents: UIControlEventTouchUpInside];
}
HpTerm
  • 8,151
  • 12
  • 51
  • 67
Lu Hai Tu
  • 11
  • 2
1

willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation can be used, I just tried this on my iPad running a keyboard on iOS 8.1. Note that those are deprecated in iOS 8, but their replacement, willTransitionToTraitCollection, isn't called though likely because the trait collection doesn't change for the keyboard upon rotation.

Jordan H
  • 52,571
  • 37
  • 201
  • 351
1

Before iOS 8.3, you have to use

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                               duration:(NSTimeInterval)duration

This one does not work (it should be a bug):

-(void)viewWillTransitionToSize:(CGSize)size
      withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

On iOS 8.3 and later, you'd better use

-(void)viewWillTransitionToSize:(CGSize)size
      withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

because

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                               duration:(NSTimeInterval)duration

is deprecated.

Adil Soomro
  • 37,609
  • 9
  • 103
  • 153
Vince Yuan
  • 10,533
  • 3
  • 32
  • 27
  • well i think viewWillTransitionToSize stilled worked before 8.3, except you had to call this function on each subview's controller, where as now you don't – lzl May 30 '15 at 16:07
  • Okay, I take it back, I got confused. Tried it and it's still working like before. Just to clarify, viewWillTransitionToSize gets picked up by the first view controller only, and from there you'd either have to rely on viewDidLayoutSubviews propagation or call your custom direction change functions on all the subviews. Which is probably how it was always intended to work anyway. That's how i've been using it even before 8.3 anyhow. – lzl Jun 02 '15 at 06:52
1

In somecase [UIScreen mainscreen].bounds may not work. Sometimes it will update after viewWillTransitionToSize:

Try this

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{

    CGSize screenSize = [[UIScreen mainScreen] bounds].size;
    CGFloat realScreenHeight = MAX(screenSize.height, screenSize.width);
    if(size.width == realScreenHeight)
        NSLog(@"Landscape");
    else
        NSLog(@"Portrait");
}
Wanpaya
  • 49
  • 6
0

for those who are searching for the answer in Swift 5.

by override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) method. you can detect device orientation.

here is the code for my custom keyboard.

override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
        let screen = UIScreen.main.bounds
        if screen.width < screen.height {
            print("!!! portrait")
            let constraintForHeight:NSLayoutConstraint = NSLayoutConstraint(item: mainView!, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 0, constant: 325)
            constraintForHeight.isActive = true
            constraintForHeight.priority = UILayoutPriority.defaultHigh
            self.inputView?.addConstraint(constraintForHeight)
        } else {
            print("!!! landspace")
            let constraintForHeight:NSLayoutConstraint = NSLayoutConstraint(item: mainView!, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 0, constant: 210)
            constraintForHeight.isActive = true
            constraintForHeight.priority = UILayoutPriority.defaultHigh
            self.inputView?.addConstraint(constraintForHeight)
        }
    }
Ketan Odedra
  • 1,215
  • 10
  • 35
-2
      - (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator 
    {   
     [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) 
        {      UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];      // do whatever 
          } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {    }];       [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator]; 
 }
Daxesh Nagar
  • 1,405
  • 14
  • 22
  • Wrong. "'sharedApplication' is unavailable: not available on iOS (App Extension) - Use view controller based solutions where appropriate instead." – Bruno Philipe Sep 25 '14 at 21:45