5

Since iOS 8.0, the function didRotateFromInterfaceOrientation becomes deprecated and I now have no idea how to change the orientation of AVCaptureVideoPreviewLayer properly.

According to "interfaceOrientation" is deprecated in iOS 8, How to change this method Objective C, I have the following working code to get the AVCaptureVideoOrientation based on statusBarOrientation:

- (AVCaptureVideoOrientation)getOrientation {
    // Translate the orientation
    switch ([[UIApplication sharedApplication] statusBarOrientation]) {
        case UIInterfaceOrientationPortrait:
            return AVCaptureVideoOrientationPortrait;
        case UIInterfaceOrientationPortraitUpsideDown:
            return AVCaptureVideoOrientationPortraitUpsideDown;
        case UIInterfaceOrientationLandscapeLeft:
            return AVCaptureVideoOrientationLandscapeLeft;
        case UIInterfaceOrientationLandscapeRight:
            return AVCaptureVideoOrientationLandscapeRight;
        default:
            return AVCaptureVideoOrientationPortrait;
    }
}

I need to call this following function when the orientation is changed:

[self.videoPreviewLayer.connection setVideoOrientation:[self getOrientation]];

But I don't know where is the right place to put it.

Attempt 1

Since didRotateFromInterfaceOrientation is deprecated and Apple suggest to use viewWillTransitionToSize:withTransitionCoordinator:, this seems natural to try:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        [self.videoPreviewLayer.connection setVideoOrientation:[self getOrientation]];
    }];
}

But this function doesn't get called if the UIViewController is being presented (seems to be a known bug based on "viewWillTransitionToSize:" not called in iOS 9 when the view controller is presented modally). Even if this bug is resolved, there is another problem: orientation change is not equivalent to size change. For instance, if a view is presented with UIModalPresentationFormSheet as the following,

SecondViewController *vc = [[SecondViewController alloc] init];
vc.view.backgroundColor = [UIColor greenColor];
vc.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:vc animated:true completion:nil];

the presented view size the same under landscape and portrait mode for iPad:

Therefore, I cannot simply use viewWillTransitionToSize.

Attempt 2

I notice that UIDeviceOrientationDidChangeNotification is not deprecated. Great! I tried register a notification with that:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // Some other code to set up the preview layer 
    // ...

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(deviceOrientationDidChange:)
                                                 name:UIDeviceOrientationDidChangeNotification
                                               object:nil];
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
}

- (void)deviceOrientationDidChange:(NSNotification *)notification {
    [self.videoPreviewLayer.connection setVideoOrientation:[self getOrientation]];
}

This is almost working and it is so closed! The problem is that when I get the device orientation changed notification, the animation of rotating the view maybe started and maybe not. Or in another word, the return value [[UIApplication sharedApplication] statusBarOrientation] may be incorrect if the device is already rotated up side down but the animation is still pending.

And I don't know what else to try :( Any ideas?

Community
  • 1
  • 1
Yuchen
  • 30,852
  • 26
  • 164
  • 234
  • Why don't you use UIDeviceOrientation in the "deviceOrientationDidChange" method, instead of status bar orientation? Just ignore .FaceUp and .FaceDown orientations. – almas Jan 13 '16 at 20:26
  • @almas, thanks for the comment. But the problem is that the device orientation is not exactly the same as interface orientation. It could be possible that the device is up side down, but the view does not support .FaceDown. If I can guarantee that my current view supports all four orientation, then, yes, I think your suggestion is okay here! – Yuchen Jan 13 '16 at 20:36
  • Well, you just ignore all orientations that your app doesn't support, for example, you can ignore FaceUp, FaceDown, and PortraitUpsideDown. Give it a try, I'm sure it will work. – almas Jan 13 '16 at 22:53

1 Answers1

4

The following should be able to handle orientation of video preview properly in swift 2.1:

override func viewWillAppear(animated: Bool) {
    super.viewDidLoad()

    NSNotificationCenter.defaultCenter().addObserver(self,
        selector: Selector("deviceOrientationDidChange:"),
        name: UIDeviceOrientationDidChangeNotification,
        object: nil)
}

override func viewDidDisappear(animated: Bool) {
    NSNotificationCenter.defaultCenter().removeObserver(self)
    super.viewDidDisappear(animated)
}

func deviceOrientationDidChange(sender: AnyObject) {
    let deviceOrientation = UIDevice.currentDevice().orientation;
    switch (deviceOrientation)
    {
    case .Portrait:
       previewLayer.connection.videoOrientation = .Portrait
    case .LandscapeLeft:
       previewLayer.connection.videoOrientation = .LandscapeRight
    case .LandscapeRight:
        previewLayer.connection.videoOrientation = .LandscapeLeft
    case .PortraitUpsideDown:
       previewLayer.connection.videoOrientation = .PortraitUpsideDown
    default:
        break
    }
}
Yuchen
  • 30,852
  • 26
  • 164
  • 234
  • 1
    I wish Apple would just implement your way behind the scenes. Fine work. You can still use the deprecated `didRotate`, but your implementation actually accounts for a couple edge cases that `didRotate` doesn't. – ScottyBlades Dec 04 '18 at 03:20