AVCaptureVideoPreviewLayer orientation - need landscape
Solution 1
The default camera orientation is Landscape Left (home button one the left). You need to do two things here:
1- Change the previewLayer frame to:
self.previewLayer.frame=self.view.bounds;
You need to set the preview layer frame to the bounds of the screen so that the frame of the preview layer changes when the screen rotates (you cannot use frame of the root view because that does not change with rotation but the bounds of the root view do). In your example, you are setting the previewlayer frame to a previewView property which I do not see.
2- You need to rotate the preview layer connection with the rotation of the device. Add this code in viewDidAppear:
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
//Get Preview Layer connection
AVCaptureConnection *previewLayerConnection=self.previewLayer.connection;
if ([previewLayerConnection isVideoOrientationSupported])
[previewLayerConnection setVideoOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
}
Hope this solves it.
Full Disclosure: This is a simplified version since you do not care if Landscape right or Landscape left.
Solution 2
Swift 5.5, Xcode 13.2
private func updatePreviewLayer(layer: AVCaptureConnection, orientation: AVCaptureVideoOrientation) {
layer.videoOrientation = orientation
self.previewLayer?.frame = view.bounds
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let connection = self.previewLayer?.connection {
let currentDevice = UIDevice.current
let orientation: UIDeviceOrientation = currentDevice.orientation
let previewLayerConnection: AVCaptureConnection = connection
if previewLayerConnection.isVideoOrientationSupported {
switch orientation {
case .portrait: self.updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)
case .landscapeRight: self.updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeLeft)
case .landscapeLeft: self.updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeRight)
case .portraitUpsideDown: self.updatePreviewLayer(layer: previewLayerConnection, orientation: .portraitUpsideDown)
default: self.updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)
}
}
}
}
Swift 2.2, Xcode 7.3
private func updatePreviewLayer(layer: AVCaptureConnection, orientation: AVCaptureVideoOrientation) {
layer.videoOrientation = orientation
previewLayer.frame = self.view.bounds
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let connection = self.previewLayer?.connection {
let currentDevice: UIDevice = UIDevice.currentDevice()
let orientation: UIDeviceOrientation = currentDevice.orientation
let previewLayerConnection : AVCaptureConnection = connection
if (previewLayerConnection.supportsVideoOrientation) {
switch (orientation) {
case .Portrait: updatePreviewLayer(previewLayerConnection, orientation: .Portrait)
case .LandscapeRight: updatePreviewLayer(previewLayerConnection, orientation: .LandscapeLeft)
case .LandscapeLeft: updatePreviewLayer(previewLayerConnection, orientation: .LandscapeRight)
case .PortraitUpsideDown: updatePreviewLayer(previewLayerConnection, orientation: .PortraitUpsideDown)
default: updatePreviewLayer(previewLayerConnection, orientation: .Portrait)
}
}
}
}
Solution 3
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
if let connection = self.previewLayer?.connection {
let currentDevice: UIDevice = UIDevice.current
let orientation: UIDeviceOrientation = currentDevice.orientation
let previewLayerConnection : AVCaptureConnection = connection
if (previewLayerConnection.isVideoOrientationSupported) {
switch (orientation) {
case .portrait:
previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.portrait
case .landscapeRight:
previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.landscapeRight
case .landscapeLeft:
previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.landscapeLeft
case .portraitUpsideDown:
previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.portraitUpsideDown
default:
previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.portrait
}
}
}
}
Solution 4
The API seems to have changed somewhat. videoOrientation
is now a property on the preview layer's connection
property. Furthermore, no need to use a switch. Answer for Swift 3.0:
override func viewDidLayoutSubviews() {
self.configureVideoOrientation()
}
private func configureVideoOrientation() {
if let previewLayer = self.previewLayer,
let connection = previewLayer.connection {
let orientation = UIDevice.current.orientation
if connection.isVideoOrientationSupported,
let videoOrientation = AVCaptureVideoOrientation(rawValue: orientation.rawValue) {
previewLayer.frame = self.view.bounds
connection.videoOrientation = videoOrientation
}
}
}
Solution 5
We can't use
[previewLayerConnection setVideoOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
because UIInterfaceOrientation != AVCaptureVideoOrientation
But we can just test values... and this work,with following code.
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
switch (orientation) {
case UIInterfaceOrientationPortrait:
[_videoPreviewLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
break;
case UIInterfaceOrientationPortraitUpsideDown:
[_videoPreviewLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];
break;
case UIInterfaceOrientationLandscapeLeft:
[_videoPreviewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
break;
case UIInterfaceOrientationLandscapeRight:
[_videoPreviewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
break;
}
}
soleil
Updated on July 05, 2022Comments
-
soleil almost 2 years
My app is landscape only. I'm presenting the AVCaptureVideoPreviewLayer like this:
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session]; [self.previewLayer setBackgroundColor:[[UIColor blackColor] CGColor]]; [self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; NSLog(@"previewView: %@", self.previewView); CALayer *rootLayer = [self.previewView layer]; [rootLayer setMasksToBounds:YES]; [self.previewLayer setFrame:[rootLayer bounds]]; NSLog(@"previewlayer: %f, %f, %f, %f", self.previewLayer.frame.origin.x, self.previewLayer.frame.origin.y, self.previewLayer.frame.size.width, self.previewLayer.frame.size.height); [rootLayer addSublayer:self.previewLayer]; [session startRunning];
self.previewView has a frame of (0,0,568,320), which is correct. self.previewLayer logs a frame of (0,0,568,320), which is theoretically correct. However, the camera display appears as a portrait rectangle in the middle of the landscape screen, and the orientation of the camera preview image is wrong by 90 degrees. What am I doing wrong? I need the camera preview layer to appear in the full screen, in landscape mode, and the image should be orientated correctly.
-
soleil over 11 yearsThanks, this works. I had actually gotten it to work with self.previewLayer.orientation = UIInterfaceOrientationLandscapeLeft, but I wasn't comfortable with that because it is deprecated. How could I add support for both Landscape Left and Right?
-
alones almost 11 yearsHowever, the code needs over iOS 6.0 because self.previewLayer.connection; So use captureVideoPreviewLayer.orientation = UIInterfaceOrientationLandscapeLeft;
-
netshark1000 about 9 yearsAdd a default: break;
-
Matej about 9 yearssolution to solve that is to override the
viewDidLayoutSubviews
method in your view controller and update bounds accordingly -
DrPatience over 8 yearswhen i use this code with the device in landscape mode, the frame of the preview layer is not in the proper position instead it appears on the bottom left corner, can you please suggest how I fix this ? thanks
-
Olivier de Jonge over 8 years@DrPatience, you can overcome this when you set the frame of the previewLayer again: previewLayer?.frame = CGRectMake(0, 0, size.width, size.height)
-
Fattie about 8 yearsThis is incredibly beautiful.
-
Fattie about 8 yearsSome great code here ... drivecurrent.com/devops/…
-
Ajay Sharma almost 8 yearsWell calling updateCameraLayer worked for me. +1 from me ;)
-
CodeBender almost 8 yearsThis answer fails if you rotate quickly from one landscape mode to the other. The layout method does not get called in that scenario. I also tested with viewWillLayout and get the same result.
-
Fox5150 almost 8 yearsIn the switch, 2 cases (LandscapeRight and LandscapeLeft) are inverted but it works great.
-
Frank Hintsch over 7 yearsI do call updateCameraLayer in viewDidAppear. Some minor changes were necessary:
float x = mCameraView.bounds.origin.x
and the firstmCameraLayer.frame = mCameraView.bounds;
can be omitted. -
Dylan Hand about 7 yearsCareful -
UIDevice.currentDevice().orientation
returns the orientation of the device regardless of whether the user interface supports it. It's better to compare againstUIApplication.shared.statusBarOrientation
to ensure your videoPreviewLayer has the same orientation as your UI. -
bakalolo about 7 yearsHow to fix the lag? I am getting a 2 second lag on preview layer when I change orientation.
-
Leon over 6 yearsThis solution worked perfectly for me until iOS 11/Swift 4. This fixed it for me stackoverflow.com/a/34480478/945247
-
Juguang over 6 yearsYou do not have to write break inside swift's switch, in the way of C.
-
user2363025 over 6 years@Maselko i tried to use your code but am still having trouble. I have a question here if you have time to take a look: stackoverflow.com/questions/46913953/…
-
user2363025 over 6 yearshow come right and left are inverted? I am still havingtrouble saving the image from this preview layer with correct orientation. Q here if you have any time: stackoverflow.com/questions/46913953/…
-
Nick Yap over 6 yearsThis should be the answer. Thanks!
-
Rob over 6 yearsAll of these
break
statements are unnecessary. Swiftswitch
cases do not fall through by default, like they do in Objective-C. -
Rob over 6 yearsAll of these
break
statements are unnecessary. Swiftswitch
cases do not fall through by default, like they do in Objective-C. Also,previewLayerConnection.videoOrientation
is known to beAVCaptureVideoOrientation
, so you can simplify statements topreviewLayerConnection.videoOrientation = .portrait
. -
Rob over 6 yearsRemove those
break
statements. This is Swift with no fallthrough inswitch
statements like Objective-C does. -
algal over 6 years
statusBarOrientation
was deprecated in iOS 9, so it's no longer wise to use. Also, it returns aUIDeviceOrientation
which is a different type thanAVCaptureVideoOrientation
, so it may be undefined what will happen if you, for instance, are in an undefined device orientation or place your device flat on the table. -
JKvr over 6 yearsReally nice. A little fix; the last line should be
connection.videoOrientation = videoOrientation
(removepreviewLayer.
) -
Bradley about 6 yearsShould be the answer, it works. previewLayer.connection is nullable it should be in the las line: previewLayer.connection?.videoOrientation = videoOrientation
-
Chuck Krutsinger about 6 yearsCorrect about the mismatch between UIKit and AVFoundation orientations. That was tripping me up. In my case, I also had to change the previewLayer's frame to match the new size using: previewLayer.position = CGPointMake(size.width/2.0, size.height/2.0) previewLayer.frame = CGRectMake(0, 0, size.width, size.height)
-
haxpor almost 6 yearsAs per algal suggest, using this nowadays we can check current orientation via
UIApplication.shared.statusBarOrientation
then set video orientation viaAVCaptureVideoOrientation
. It maps one-to-one. -
iThompkins over 4 yearsthis is eee1337
-
Vincenzo over 4 yearsGreat!! this works great also for Swift4 and iOS 9 .
-
aheze over 2 yearsSimple and nice. Should be accepted.