Objective-C
iOS
UIKit
Swift
UIWindow

iOS8にて、[UIWindow new]で生成したWindowのframe原点が、Landscape状態の画面にも関わらず、Portrait左上になってしまう現象を解決した

iOS 8からframeなど座標を考える上で、スクリーン上で見えているOrientationの左上が原点となるようになった。
月日は流れiOS 11も出る頃なので、いい加減切りたいバージョンも増えつつある今日このごろ、
[UIWindow new]を使用するにあたって、iOS 8系でのみ発生する不具合のような挙動に直面した。

多分Swiftでも起こるのでタグ付けした。

何が起こるか

iOS 8でだけ、UIWindowを生成して表示したとき、Landscape表示しているにも関わらずPortraitで表示される。

どういう状況で起こるか

  • iOS 8.xである(iOS 7.x以下、iOS 9.x以上では発生しない)
  • [UIWindow new]でWindowを生成する
  • 生成したWindowは[vc shouldAutorotate] == NOとなるViewControllerrootViewControllerに持つ
  • 生成したWindowのframeをフルスクリーンにしようと[UIScreen mainScreen].boundsの値を使う
  • Landscape状態でWindowを生成・表示する

以下は要点を抽出したコード。

// SampleViewController.h
@interface SampleViewController : UIViewController
@end
// SampleViewController.m
@implementation SampleViewController
- (BOOL)shouldAutoRotate {
    return NO;
}
@end
// ViewController.m
@interface ViewController ()
@property (nonatomic) UIWindow *w;
@end

@implementation ViewController
- (IBACtion)openWindow:(UIButton*)sender {
    UIWindow *w = [UIWindow new];
    w.frame = [UIScreen mainScreen].bounds;
    w.rootViewController = [SampleViewController new];

    self.w = w;
    [self.w makeKeyAndVisible];
}
@end

これが

Simulator Screen Shot 2017.08.04 11.50.52.png

こうなる

screenshot.png

iOS 8だけ、UIScreenの原点と生成したWindowの原点が異なっている。

  • UIScreenは見えているスクリーンの左上を原点としている
  • [UIWindow new]によって生成されたUIWindowPortrait状態の左上を原点としている

HACKに解決

shouldAutoRotateYESなUIViewControllerを持たせた上でmakeKeyAndVisibleかけたら上手くいくんじゃね?という発想のもと、以下のようにしたところ上手くいった。

- (IBACtion)openWindow:(UIButton*)sender {
    UIWindow *w = [UIWindow new];
    w.frame = [UIScreen mainScreen].bounds;

    // HACK: iOS 8系にてShouldAutoRotate == NO のViewControllerを
    //       rootViewControllerに持ってmakeKeyAndVisibleを実行すると
    //       アプリケーションのInterfaceOrientationに関わらず
    //       InterfaceOrientationPortrait状態のWindowが表示されてしまう
    //       iOS 9以降では空のUIViewControllerを持たせる必要はなく、
    //       表示したいViewControllerを持たせて構わない
    w.rootViewController = [UIViewController];

    self.w = w;
    [self.w makeKeyAndVisible];

    // HACK: iOS 8系にてShouldAutoRotate == NO のViewControllerを
    //       rootViewControllerに持ってmakeKeyAndVisibleを実行すると
    //       アプリケーションのInterfaceOrientationに関わらず
    //       InterfaceOrientationPortrait状態のWindowが表示されてしまう
    //       よって空のViewControllerを持たせてmakeKeyAndVisibleを実行した後に
    //       ViewControllerを差し替える
    w.rootViewController = [SampleViewController new];
}

直前まで表示されていたWindowのInterfaceOrientationを元に、表示したいWindowにtransformかけるしかないのか…?という悩みから解決されて最高に気分がいい。