2012年9月22日土曜日

iOS 6 UINavigationControllerのAutorotate



iOS6ではshouldAutorotateToInterfaceOrientation:メソッドが廃止され、代わりにsupportedInterfaceOrientationsメソッドとshouldAutorotateメソッドを使うようになっています。
ところが最初のViewControllerをUINavigationControllerにしているアプリでは、他のViewControllerにこれらのメソッドを実装しても有効になりません。(shouldAutorotateが呼ばれない。)
このようなアプリの場合は次のようにすると回転を制御できるようになります。
  • UINavigationControllerのサブクラスを作り、起動時のUINavigationControllerのクラスをそれに替える。
  • 上記サブクラスにsupportedInterfaceOrientationsメソッドとshouldAutorotateメソッドを実装する。
  • 一部の画面だけ縦・横の回転を行えうようにする場合、このサブクラスのsupportedInterfaceOrientationsが返す値を画面に応じて変更する。
実装例:
@interface MyNavigationController : UINavigationController
@end

@implementation MyNavigationController

- (NSUInteger)supportedInterfaceOrientations
{
    if ([[self.viewControllers lastObject] isKindOfClass:[RotatableViewController class]]) {
        return UIInterfaceOrientationMaskAllButUpsideDown; 
    } else {
        return UIInterfaceOrientationMaskPortrait;
    }
}

- (BOOL)shouldAutorotate
{
    return YES;
}
@end

shouldAutorotateToInterfaceOrientation:はiOS 6では呼ばれないため、縦横位置に応じたレイアウト調整などをこのメソッドで行っていた場合はこのメソッドの代わりにwillRotateToInterfaceOrientation:duration:メソッドを利用することができます。このメソッドはiOS 5でも呼ばれるため、iOS 6のときだけ必要な処理を行うようにします。また、このようにすればiOS5、iOS6両方に対応可能になります。

実装例:
@implementation RotatableViewController
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    if ([self respondsToSelector:@selector(shouldAutorotate)]) {
        [self shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
    }
}
...
@end

If you want this article in English, let me know.

補足

View Programming Guide for iOSに次のように書かれています。

Dynamically Controlling Whether Rotation Occurs
If you want to temporarily disable automatic rotation, avoid manipulating the orientation masks to do this. Instead, override the shouldAutorotate method on the topmost view controller. This method is called before performing any autorotation. If it returns NO, then the rotation is suppressed.

上記の例の場合は初期のUINavigationControllerが"topmost view controller"になっていると考えられます。この方法でうまくいかない場合は別のWindowが"topmost view controller"になっているというこになります。おそらくはroot view controllerと思われるので、次のメソッドで取得できるUINavigationControllerに対して、上記の方法を適用してみてください。
[myNavContoller.viewControllers.objectAtIndex:0]

ModalViewの場合はそれをコントロールするUINavigationControllerとなります。