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
- Capture will appear rotated (as if you're holding the camera 90 degrees off)
- 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.