How to detect Orientation Change in Custom Keyboard Extension in iOS 8?
Asked Answered
G

10

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?

Gemmulation answered 11/6, 2014 at 15:29 Comment(8)
I encourage you to change your accepted answer. As the current solution does not work for iPads.Carbolated
Why? For iPad you can change view frame size width to 768 for Portrait. For Landscape you can do it with if else.Gemmulation
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. macrumors.com/roundup/iphone-6Carbolated
Yes. When they release devices with new size , we can detect View.frame.size.width or height for each devices.Gemmulation
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.Carbolated
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.Carbolated
Added best solution working on all screen sizes and using strictly non-deprecated methods here: https://mcmap.net/q/23457/-how-to-detect-orientation-change-in-custom-keyboard-extension-in-ios-8Carbolated
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.Carbolated
G
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!

Grefer answered 28/6, 2014 at 13:23 Comment(15)
viewDidlayoutSubviews is NOT called when rotate to LandscapeTrituration
@iAn It's working in my app when rotate to Landscape.Gemmulation
@Sabo you should mark this as the answered response. works on my endTestosterone
Magic numbers... oh the horror. Don't do this!Foliated
@Joey Here is my non-deprecated method without magic numbers: https://mcmap.net/q/23457/-how-to-detect-orientation-change-in-custom-keyboard-extension-in-ios-8Carbolated
Use - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration insteadWether
surprisingly even for iPhone 6 plus the width of keyboard is still 320!Glassine
@EthanLong Just checked on iPhone6+ simulator, it returned width value of 414.00 ... NSLog: WIDTH IS: 414.000000Carbolated
@Albert Renshaw, maybe it's because I did not provide splash screen for iPhone 6 and plus? I always get 320 from the simulatorsGlassine
@EthanLong Strange, I didn't either, new project, are you running the script in viewDidAppear?Carbolated
@AlbertRenshaw not sure about extensions, last time I checked (beta 6?) it was all 320. But for app's if you don't provide large launch screens it must be 320.Glassine
@EthanLong I provided NO launch screen and ran it on the actual live iOS8 version and it returned 414 on iPhone 6 PLUS (Are you sure you ran the plus?)Carbolated
I have the same but how to get device is landScapeLeft or landScapeRight like this one #25462591Hibernaculum
On iPad, inside viewDidLayoutSubviews, [[UIScreen mainScreen] bounds] has the old values before orientation change:/Sapienza
Works for me. Currently using it in my app.Jipijapa
C
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");
    }
}
Carbolated answered 9/8, 2014 at 19:42 Comment(9)
On iPhone 6 simulator, it prints out Landscape even though the device is in portrait mode.Demb
@SalavatKhanov Works fine for me in iPhone6 simulator. Which xCode BETA are you running?Carbolated
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.Publius
@fatshu The logic still works just fine? It's just the numbers are off hahaCarbolated
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 #25462591Hibernaculum
oh dear this is so hacky, apple need to get on their damn gameMuncy
@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.Carbolated
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.Muncy
@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 :DCarbolated
B
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;
Bartolomeo answered 28/9, 2014 at 23:35 Comment(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;Mouldon
T
3

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

Trier answered 30/6, 2014 at 7:13 Comment(5)
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.Fuddle
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.Trier
My Radar issue was marked as a duplicate of 17472132, which is currently still open. Hopefully that means this solution will eventually work.Fuddle
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.)Fuddle
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.Vancevancleave
O
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];
}
Orms answered 12/11, 2014 at 12:1 Comment(0)
F
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.

Foliated answered 15/11, 2014 at 5:23 Comment(0)
N
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.

Nineteenth answered 27/4, 2015 at 8:0 Comment(2)
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'tDantzler
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.Dantzler
S
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");
}
Stere answered 28/11, 2016 at 14:11 Comment(0)
B
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)
        }
    }
Belvia answered 8/11, 2019 at 9:43 Comment(0)
F
-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]; 
 }
Foolish answered 22/9, 2014 at 8:1 Comment(1)
Wrong. "'sharedApplication' is unavailable: not available on iOS (App Extension) - Use view controller based solutions where appropriate instead."Grizelda

© 2022 - 2024 — McMap. All rights reserved.