AVCaptureVideoPreviewLayer orientation - need landscape

57,002

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;
    }
}
Share:
57,002
soleil
Author by

soleil

Updated on July 05, 2022

Comments

  • soleil
    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
    soleil over 11 years
    Thanks, 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
    alones almost 11 years
    However, the code needs over iOS 6.0 because self.previewLayer.connection; So use captureVideoPreviewLayer.orientation = UIInterfaceOrientationLandscapeLeft;
  • netshark1000
    netshark1000 about 9 years
    Add a default: break;
  • Matej
    Matej about 9 years
    solution to solve that is to override the viewDidLayoutSubviews method in your view controller and update bounds accordingly
  • DrPatience
    DrPatience over 8 years
    when 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
    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
    Fattie about 8 years
    This is incredibly beautiful.
  • Fattie
    Fattie about 8 years
    Some great code here ... drivecurrent.com/devops/…
  • Ajay Sharma
    Ajay Sharma almost 8 years
    Well calling updateCameraLayer worked for me. +1 from me ;)
  • CodeBender
    CodeBender almost 8 years
    This 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
    Fox5150 almost 8 years
    In the switch, 2 cases (LandscapeRight and LandscapeLeft) are inverted but it works great.
  • Frank Hintsch
    Frank Hintsch over 7 years
    I do call updateCameraLayer in viewDidAppear. Some minor changes were necessary: float x = mCameraView.bounds.origin.x and the first mCameraLayer.frame = mCameraView.bounds; can be omitted.
  • Dylan Hand
    Dylan Hand about 7 years
    Careful - UIDevice.currentDevice().orientation returns the orientation of the device regardless of whether the user interface supports it. It's better to compare against UIApplication.shared.statusBarOrientation to ensure your videoPreviewLayer has the same orientation as your UI.
  • bakalolo
    bakalolo about 7 years
    How to fix the lag? I am getting a 2 second lag on preview layer when I change orientation.
  • Leon
    Leon over 6 years
    This solution worked perfectly for me until iOS 11/Swift 4. This fixed it for me stackoverflow.com/a/34480478/945247
  • Juguang
    Juguang over 6 years
    You do not have to write break inside swift's switch, in the way of C.
  • user2363025
    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
    user2363025 over 6 years
    how 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
    Nick Yap over 6 years
    This should be the answer. Thanks!
  • Rob
    Rob over 6 years
    All of these break statements are unnecessary. Swift switch cases do not fall through by default, like they do in Objective-C.
  • Rob
    Rob over 6 years
    All of these break statements are unnecessary. Swift switch cases do not fall through by default, like they do in Objective-C. Also, previewLayerConnection.videoOrientation is known to be AVCaptureVideoOrientation, so you can simplify statements to previewLayerConnection.videoOrientation = .portrait.
  • Rob
    Rob over 6 years
    Remove those break statements. This is Swift with no fallthrough in switch statements like Objective-C does.
  • algal
    algal over 6 years
    statusBarOrientation was deprecated in iOS 9, so it's no longer wise to use. Also, it returns a UIDeviceOrientation which is a different type than AVCaptureVideoOrientation, 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
    JKvr over 6 years
    Really nice. A little fix; the last line should be connection.videoOrientation = videoOrientation (remove previewLayer.)
  • Bradley
    Bradley about 6 years
    Should be the answer, it works. previewLayer.connection is nullable it should be in the las line: previewLayer.connection?.videoOrientation = videoOrientation
  • Chuck Krutsinger
    Chuck Krutsinger about 6 years
    Correct 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
    haxpor almost 6 years
    As per algal suggest, using this nowadays we can check current orientation via UIApplication.shared.statusBarOrientation then set video orientation via AVCaptureVideoOrientation. It maps one-to-one.
  • iThompkins
    iThompkins over 4 years
    this is eee1337
  • Vincenzo
    Vincenzo over 4 years
    Great!! this works great also for Swift4 and iOS 9 .
  • aheze
    aheze over 2 years
    Simple and nice. Should be accepted.