AVCaptureVideoPreviewLayer landscape orientation

override func viewWillLayoutSubviews() {
    self.previewLayer.frame = self.view.bounds
    if previewLayer.connection.isVideoOrientationSupported {
        self.previewLayer.connection.videoOrientation = self.interfaceOrientation(toVideoOrientation: UIApplication.shared.statusBarOrientation)
    }
}

func interfaceOrientation(toVideoOrientation orientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
    switch orientation {
    case .portrait:
        return .portrait
    case .portraitUpsideDown:
        return .portraitUpsideDown
    case .landscapeLeft:
        return .landscapeLeft
    case .landscapeRight:
        return .landscapeRight
    default:
        break
    }

    print("Warning - Didn't recognise interface orientation (\(orientation))")
    return .portrait
}

//SWIFT 3 CONVERSION


Swift 5 (iOS 13.0+):

    func updateVideoOrientation() {
        guard let videoPreviewLayer = self.videoPreviewLayer else {
            return
        }
        guard videoPreviewLayer.connection!.isVideoOrientationSupported else {
            print("isVideoOrientationSupported is false")
            return
        }
        let statusBarOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
        let videoOrientation: AVCaptureVideoOrientation = statusBarOrientation?.videoOrientation ?? .portrait
        videoPreviewLayer.frame = view.layer.bounds
        videoPreviewLayer.connection?.videoOrientation = videoOrientation
        videoPreviewLayer.removeAllAnimations()
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        coordinator.animate(alongsideTransition: nil, completion: { [weak self] (context) in
            DispatchQueue.main.async(execute: {
                self?.updateVideoOrientation()
            })
        })
    }

extension UIInterfaceOrientation {
    var videoOrientation: AVCaptureVideoOrientation? {
        switch self {
        case .portraitUpsideDown: return .portraitUpsideDown
        case .landscapeRight: return .landscapeRight
        case .landscapeLeft: return .landscapeLeft
        case .portrait: return .portrait
        default: return nil
        }
    }
}


Swift3

func updateVideoOrientation() {
    guard let previewLayer = self.previewLayer else {
        return
    }
    guard previewLayer.connection.isVideoOrientationSupported else {
        print("isVideoOrientationSupported is false")
        return
    }

    let statusBarOrientation = UIApplication.shared.statusBarOrientation
    let videoOrientation: AVCaptureVideoOrientation = statusBarOrientation.videoOrientation ?? .portrait

    if previewLayer.connection.videoOrientation == videoOrientation {
        print("no change to videoOrientation")
        return
    }

    previewLayer.frame = cameraView.bounds
    previewLayer.connection.videoOrientation = videoOrientation
    previewLayer.removeAllAnimations()
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    coordinator.animate(alongsideTransition: nil, completion: { [weak self] (context) in
        DispatchQueue.main.async(execute: {
            self?.updateVideoOrientation()
        })
    })
}

extension UIInterfaceOrientation {
    var videoOrientation: AVCaptureVideoOrientation? {
        switch self {
        case .portraitUpsideDown: return .portraitUpsideDown
        case .landscapeRight: return .landscapeRight
        case .landscapeLeft: return .landscapeLeft
        case .portrait: return .portrait
        default: return nil
        }
    }
}

First, the answer

- (void)viewWillLayoutSubviews {
  _captureVideoPreviewLayer.frame = self.view.bounds;
  if (_captureVideoPreviewLayer.connection.supportsVideoOrientation) {
    _captureVideoPreviewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
  }
}

- (AVCaptureVideoOrientation)interfaceOrientationToVideoOrientation:(UIInterfaceOrientation)orientation {
  switch (orientation) {
    case UIInterfaceOrientationPortrait:
        return AVCaptureVideoOrientationPortrait;
    case UIInterfaceOrientationPortraitUpsideDown:
        return AVCaptureVideoOrientationPortraitUpsideDown;
    case UIInterfaceOrientationLandscapeLeft:
        return AVCaptureVideoOrientationLandscapeLeft;
    case UIInterfaceOrientationLandscapeRight:
        return AVCaptureVideoOrientationLandscapeRight;
    default:
        break;
  }
  NSLog(@"Warning - Didn't recognise interface orientation (%d)",orientation);
  return AVCaptureVideoOrientationPortrait;
}

I've come across several SO posts on this matter, and couldn't find any simple explanation so I thought I'd share my own.

If you follow Apple's sample on the matter, you'll run into two potential problems when you rotate your iOS device to landscape

  1. Capture will appear rotated (as if you're holding the camera 90 degrees off)
  2. It won't span its set bounds entirely

The problem here is that 'CALayer' doesn't support autorotation hence, unlike a 'UIView' you'd add as a subview, it won't rotate when its parent 'UIView' rotates. Therefore, you have to manually update its frame every time the parent view's bounds changes (not parent view's frame since frame stays the same after rotation). This is achieved by overriding 'viewWillLayoutSubviews' in the container view controller.

Secondly, you should use 'videoOrientation' property to inform AVFoundation about the orientation so it does the preview properly.

Hope this helps.