甘いものが好きです

iOS App開発時に感じた疑問や課題、その他の雑感などを書いていきます。

UIImagePickerControllerでデバイスの向きの変更を無視する

UIImagePickerControllerを利用して写真を撮影するとき、Portrait固定にしたいのになかなかできず苦労した。

失敗例

以下の方法を試してみたが、うまくいかなかった。

  • Xcodeのビルドターゲットの設定で「Supported Interface Orientations」を「Portrait」のみを選択する。
  • UINavigationControllerのカテゴリを作成し、そこで次のメソッドを実装し、回転の無効化(Portrait固定設定)を試みる*1
    • supportedInterfaceOrientations
    • shouldAutorotate
    • shouldAutorotateToInterfaceOrientation(iOS5以下)
  • UIImagePickerControllerのサブクラスを作成し、カメラ撮影にはこのクラスを利用する。このサブクラスでは次のメソッドを実装し、回転の無効化(Portrait固定設定)を試みる。
    • supportedInterfaceOrientations
    • shouldAutorotate
    • shouldAutorotateToInterfaceOrientation(iOS5以下)

解決策

次のページの回答にある方法で無事、デバイスの回転を無視できるようになった。

回答のソースコードではtry-catchを使っているが、カメラが利用できるかどうかの判定はUIImagePickerControllerのisSourceTypeAvailable:でできるので、少しだけ修正すると次のように書ける。

if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {

    // Create a UIImagePicker in camera mode.
    UIImagePickerController *picker = [[[UIImagePickerController alloc] init] autorelease]; 
    picker.sourceType = UIImagePickerControllerSourceTypeCamera;  
    picker.delegate = self; 

    // Prevent the image picker learning about orientation changes by preventing the device from reporting them.
    UIDevice *currentDevice = [UIDevice currentDevice];

    // The device keeps count of orientation requests.  If the count is more than one, it continues detecting them and sending notifications.  So, switch them off repeatedly until the count reaches zero and they are genuinely off.
    // If orientation notifications are on when the view is presented, it may slide on in landscape mode even if the app is entirely portrait.
    // If other parts of the app require orientation notifications, the number "end" messages sent should be counted.  An equal number of "begin" messages should be sent after the image picker ends.
    while ([currentDevice isGeneratingDeviceOrientationNotifications])
        [currentDevice endGeneratingDeviceOrientationNotifications];

    // Display the camera.
    [self presentModalViewController:picker animated:YES];

    // The UIImagePickerController switches on notifications AGAIN when it is presented, so switch them off again.
    while ([currentDevice isGeneratingDeviceOrientationNotifications])
        [currentDevice endGeneratingDeviceOrientationNotifications];
}

あと、この回答では触れられていないけれど、UIImgaePickerControllerをdismissしてからViewの回転を明示的に許可する必要がある。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    
    [picker dismissViewControllerAnimated:YES completion:^{
        UIDevice *currentDevice = [UIDevice currentDevice];
        while (![currentDevice isGeneratingDeviceOrientationNotifications])
            [currentDevice beginGeneratingDeviceOrientationNotifications];
    }];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    
    [picker dismissViewControllerAnimated:YES completion:^{
        UIDevice *currentDevice = [UIDevice currentDevice];
        while (![currentDevice isGeneratingDeviceOrientationNotifications])
            [currentDevice beginGeneratingDeviceOrientationNotifications];
    }];
}

isGeneratingDeviceOrientationNotifications、beginGeneratingDeviceOrientationNotifications、endGeneratingDeviceOrientationNotificationsは今まで使ったことがなかった(存在自体に気がついていなかった)のだけれど、一時的にViewの回転を無効化するときに役立ちそうだ。

*1:カテゴリで既存メソッドの上書きをすると、新たに定義された方のメソッドが有効になる。『詳解 Objective-C 2.0 第3版』10-02節の「既存のメソッドの上書き」を参照。