64

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.

Ashley Mills
  • 41,127
  • 14
  • 115
  • 144
soleil
  • 11,239
  • 29
  • 102
  • 172
  • 4
    **Important** For 2016, skip down to the correct answer below http://stackoverflow.com/a/36575423/294884 As with many computing topics, API details change over the years. With autolayout and so on, the older answers here (which were superb at the time) are not right now. (In another three or four years there will be a new correct answer!) – Fattie May 20 '16 at 14:07

19 Answers19

76

BEST ANSWER FOR SWIFT 3.0 AND XCODE 8.0

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.current
        
        let orientation: UIDeviceOrientation = currentDevice.orientation
        
        let previewLayerConnection : AVCaptureConnection = connection
        
        if previewLayerConnection.isVideoOrientationSupported {
            
            switch (orientation) {
            case .portrait: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)
                                
            case .landscapeRight: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeLeft)
                                
            case .landscapeLeft: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeRight)
                                
            case .portraitUpsideDown: updatePreviewLayer(layer: previewLayerConnection, orientation: .portraitUpsideDown)
                                
            default: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)
            
            }
        }
    }
}

BEST ANSWER FOR SWIFT 2.2 AND 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)
            
            }
        }
    }
}
Alex Brown
  • 38,674
  • 9
  • 88
  • 106
Maselko
  • 3,698
  • 1
  • 18
  • 21
  • 2
    This is incredibly beautiful. – Fattie May 20 '16 at 14:05
  • 1
    Some great code here ... http://drivecurrent.com/devops/using-swift-and-avfoundation-to-create-a-custom-camera-view-for-an-ios-app/#comment-4686 – Fattie May 21 '16 at 15:03
  • 6
    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. – CodeBender Aug 05 '16 at 16:07
  • 5
    In the switch, 2 cases (LandscapeRight and LandscapeLeft) are inverted but it works great. – Fox5150 Aug 07 '16 at 14:07
  • This solution worked perfectly for me until iOS 11/Swift 4. This fixed it for me https://stackoverflow.com/a/34480478/945247 – Leon Oct 01 '17 at 12:56
  • 2
    You do not have to write break inside swift's switch, in the way of C. – Juguang Oct 17 '17 at 01:39
  • @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: https://stackoverflow.com/questions/46913953/avcapture-image-orientation – user2363025 Oct 25 '17 at 10:31
  • All of these `break` statements are unnecessary. Swift `switch` cases do not fall through by default, like they do in Objective-C. – Rob Oct 31 '17 at 17:26
  • this is eee1337 – iThompkins Oct 20 '19 at 02:51
  • Great!! this works great also for Swift4 and iOS 9 . – Vincenzo Dec 14 '19 at 08:14
70

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.

Khaled Barazi
  • 8,503
  • 6
  • 39
  • 59
  • 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? – soleil Feb 25 '13 at 22:27
  • However, the code needs over iOS 6.0 because self.previewLayer.connection; So use captureVideoPreviewLayer.orientation = UIInterfaceOrientationLandscapeLeft; – alones Jul 26 '13 at 05:56
  • 1
    `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. – algal Nov 14 '17 at 05:13
  • 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. – haxpor Jun 29 '18 at 19:13
28
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
            }
        }
    }
}
Janusz Chudzynski
  • 2,620
  • 3
  • 30
  • 45
  • 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 – DrPatience Nov 16 '15 at 16:56
  • @DrPatience, you can overcome this when you set the frame of the previewLayer again: previewLayer?.frame = CGRectMake(0, 0, size.width, size.height) – Olivier de Jonge Jan 07 '16 at 17:42
  • 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. – Dylan Hand Apr 13 '17 at 23:45
  • 3
    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 Oct 31 '17 at 17:28
19

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;
    }
}
Joji Thomas Eapen
  • 1,285
  • 11
  • 28
19

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
        }
    }
}
John Rogers
  • 1,983
  • 16
  • 28
  • This should be the answer. Thanks! – Nick Yap Oct 30 '17 at 00:09
  • Really nice. A little fix; the last line should be `connection.videoOrientation = videoOrientation` (remove `previewLayer.`) – JKvr Jan 23 '18 at 17:11
  • Should be the answer, it works. previewLayer.connection is nullable it should be in the las line: previewLayer.connection?.videoOrientation = videoOrientation – Bradley Apr 19 '18 at 15:50
7

For anyone who is struggling on a full functional camera preview. Here is production code. Of course the drawback is a lag when orientation changes. If anyone have better solution to overcome this please share

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self initCamera];
}

- (void)initCamera
{
    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] error:nil];
    if (captureInput) {
        mSession = [[AVCaptureSession alloc] init];
        [mSession addInput:captureInput];
    }
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    if ([mSession isRunning]) {
        [mSession stopRunning];
        [mCameraLayer removeFromSuperlayer];

        [self initCamera];

        [self startCamera];
    }
}

- (void)startCamera
{
    [mSession startRunning];
    Settings::getInstance()->setClearColor(Color(0, 0, 0, 0));
    mCameraLayer = [AVCaptureVideoPreviewLayer layerWithSession: mSession];
    [self updateCameraLayer];
    [mCameraView.layer addSublayer:mCameraLayer];
}

- (void)stopCamera
{
    [mSession stopRunning];
    [mCameraLayer removeFromSuperlayer];
    Settings::getInstance()->setDefClearColor();
}

- (void)toggleCamera
{
    mSession.isRunning ? [self stopCamera] : [self startCamera];
    [mGLKView setNeedsDisplay];
}

- (void)updateCameraLayer
{
    mCameraLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    mCameraLayer.frame = mCameraView.bounds;
    float x = mCameraView.frame.origin.x;
    float y = mCameraView.frame.origin.y;
    float w = mCameraView.frame.size.width;
    float h = mCameraView.frame.size.height;
    CATransform3D transform = CATransform3DIdentity;
    if (UIDeviceOrientationLandscapeLeft == [[UIDevice currentDevice] orientation]) {
        mCameraLayer.frame = CGRectMake(x, y, h, w);
        transform = CATransform3DTranslate(transform, (w - h) / 2, (h - w) / 2, 0);
        transform = CATransform3DRotate(transform, -M_PI/2, 0, 0, 1);
    } else if (UIDeviceOrientationLandscapeRight == [[UIDevice currentDevice] orientation]) {
        mCameraLayer.frame = CGRectMake(x, y, h, w);
        transform = CATransform3DTranslate(transform, (w - h) / 2, (h - w) / 2, 0);
        transform = CATransform3DRotate(transform, M_PI/2, 0, 0, 1);
    } else if (UIDeviceOrientationPortraitUpsideDown == [[UIDevice currentDevice] orientation]) {
        mCameraLayer.frame = mCameraView.bounds;
        transform = CATransform3DMakeRotation(M_PI, 0, 0, 1);
    } else {
        mCameraLayer.frame = mCameraView.bounds;
    }
    mCameraLayer.transform  = transform;
}

    enter code here
Lance Mao
  • 91
  • 1
  • 5
  • solution to solve that is to override the `viewDidLayoutSubviews` method in your view controller and update bounds accordingly – Matej Jun 08 '15 at 22:29
  • Well calling updateCameraLayer worked for me. +1 from me ;) – Ajay Sharma Jul 28 '16 at 17:46
  • 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. – Frank Hintsch Dec 03 '16 at 17:34
  • How to fix the lag? I am getting a 2 second lag on preview layer when I change orientation. – bakalolo May 17 '17 at 01:16
4

As there is a deprecation and conversion warning using the above solution, and setting videoOrientation didn't seem to be working in iOS7, I put checks for orientation in my getter for the AVCaptureVideoPreviewLayer like so:

- (AVCaptureVideoPreviewLayer *) previewLayer
{
    if(!_previewLayer)
    {
        _previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: self.captureSession];

    [_previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];

    _previewLayer.frame = self.view.bounds; // Assume you want the preview layer to fill the view.

    [_previewLayer setPosition:CGPointMake(0,0)];

    if (UIDeviceOrientationLandscapeLeft == [[UIDevice currentDevice] orientation]) {
        _previewLayer.transform = CATransform3DMakeRotation(-M_PI/2, 0, 0, 1);
    }
    else if (UIDeviceOrientationLandscapeRight == [[UIDevice currentDevice] orientation])
    {
        _previewLayer.transform = CATransform3DMakeRotation(M_PI/2, 0, 0, 1);
    }
}

return _previewLayer;
}
David van Dugteren
  • 3,238
  • 8
  • 31
  • 47
3

Works with Swift 4, Xcode 9:

  override func viewWillTransition(to size: CGSize,
                                   with coordinator: UIViewControllerTransitionCoordinator)
  {
    super.viewWillTransition(to: size, with: coordinator)
    guard
    let conn = self.previewLayer?.connection,
      conn.isVideoOrientationSupported
      else { return }
    let deviceOrientation = UIDevice.current.orientation
    switch deviceOrientation {
    case .portrait: conn.videoOrientation = .portrait
    case .landscapeRight: conn.videoOrientation = .landscapeLeft
    case .landscapeLeft: conn.videoOrientation = .landscapeRight
    case .portraitUpsideDown: conn.videoOrientation = .portraitUpsideDown
    default: conn.videoOrientation = .portrait
    }
  }

One subtlety to notice here is that UIDeviceOrientation.landscapeRight goes with AVCaptureVideoOrientation.landscapeLeft.

The other landscape case is similarly mismatched. This is deliberate, and accommodates an unfortunate mismatch between UIKit and AVFoundation. If you match the cases up by matching the names, it won't work, and your video will be upside down in landscape configurations.

algal
  • 26,206
  • 12
  • 73
  • 78
  • 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) – Chuck Krutsinger Jun 07 '18 at 17:48
3

Selected answer working for Swift 4.2 - Xcode 10.0 - iOS 12.0:

var videoPreviewLayer: AVCaptureVideoPreviewLayer?

override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()
  if let previewLayerConnection =  self.videoPreviewLayer?.connection, previewLayerConnection.isVideoOrientationSupported {
    updatePreviewLayer(layer: previewLayerConnection, orientation: UIApplication.shared.statusBarOrientation.videoOrientation)
  }
}

private func updatePreviewLayer(layer: AVCaptureConnection, orientation: AVCaptureVideoOrientation) {
  layer.videoOrientation = orientation
  videoPreviewLayer?.frame = self.view.bounds
}

Don't forget the mapping from UIInterfaceOrientation to AVCaptureVideoOrientation

extension UIInterfaceOrientation {

  public var videoOrientation: AVCaptureVideoOrientation {
    switch self {
    case .portrait:
      return AVCaptureVideoOrientation.portrait
    case .landscapeRight:
      return AVCaptureVideoOrientation.landscapeRight
    case .landscapeLeft:
      return AVCaptureVideoOrientation.landscapeLeft
    case .portraitUpsideDown:
      return AVCaptureVideoOrientation.portraitUpsideDown
    default:
      return AVCaptureVideoOrientation.portrait
    }
  }

}
juliancadi
  • 840
  • 8
  • 17
3

Correct mapping from UIDeviceOrientation to AVCaptureVideoOrientation is necessary.

If your app supports device rotation, resizing preview.frame is also necessary, and this func should be called from viewDidLayoutSubviews() and viewWillTransition().

private func configureVideoOrientation() {
    if let preview = self.previewLayer,
        let connection = preview.connection {
        let orientation = UIDevice.current.orientation

        if connection.isVideoOrientationSupported {
            var videoOrientation: AVCaptureVideoOrientation
            switch orientation {
            case .portrait:
                videoOrientation = .portrait
            case .portraitUpsideDown:
                videoOrientation = .portraitUpsideDown
            case .landscapeLeft:
                videoOrientation = .landscapeRight
            case .landscapeRight:
                videoOrientation = .landscapeLeft
            default:
                videoOrientation = .portrait
            }
            preview.frame = self.view.bounds
            connection.videoOrientation = videoOrientation
        }
    }
}
Joji Thomas Eapen
  • 1,285
  • 11
  • 28
Water
  • 331
  • 4
  • 5
2

Hey Guys thanks for all the answers and feedback on this, I came across this as I am working on a swift app. You can achieve the camera to rotate with your device using this code:

override func shouldAutorotate() -> Bool {
        if your cameraController.previewLayer.connection != nil {
            var currentDevice: UIDevice = UIDevice.currentDevice()
            var orientation: UIDeviceOrientation = currentDevice.orientation

            var previewLayerConnection : AVCaptureConnection = your cameraController.previewLayer.connection

            if (previewLayerConnection.supportsVideoOrientation)
            {
                switch (orientation)
                {
                case .Portrait:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
                    break
                case .LandscapeRight:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.LandscapeLeft
                    break
                case .LandscapeLeft:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.LandscapeRight
                    break
                default:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
                    break
                }
            }

        }
        return true
    }

Hope this helps!

  • 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: https://stackoverflow.com/questions/46913953/avcapture-image-orientation – user2363025 Oct 25 '17 at 12:12
  • Remove those `break` statements. This is Swift with no fallthrough in `switch` statements like Objective-C does. – Rob Oct 31 '17 at 19:50
2

Swift 5 Version

  override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    updatePreview()
  }

  func updatePreview() {
    let orientation: AVCaptureVideoOrientation
    switch UIDevice.current.orientation {
      case .portrait:
        orientation = .portrait
      case .landscapeRight:
        orientation = .landscapeLeft
      case .landscapeLeft:
        orientation = .landscapeRight
      case .portraitUpsideDown:
        orientation = .portraitUpsideDown
      default:
        orientation = .portrait
    }
    if previewLayer?.connection?.isVideoOrientationSupported == true {
      previewLayer?.connection?.videoOrientation = orientation
    }
    previewLayer.frame = view.bounds
  }
Steven
  • 23
  • 1
  • 7
1

First we need to create the AVCaptureVideoPreviewLayer and :

  1. set its videoGravity (as in my case am using a small view to get video output).
  2. set the frame.

    [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [_videoPreviewLayer setFrame:_viewPreview.layer.bounds];
    
  3. set the orientation initially

    if (_videoPreviewLayer.connection.supportsVideoOrientation) {
            _videoPreviewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
        }
  4. set the orientation for each case using a simple switch case

    -(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;
    

    }

  5. Since the device supports both landscapeLeft and landscapeRight use the delegate called on rotation:
    - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    if (_videoPreviewLayer.connection.supportsVideoOrientation) {
        _videoPreviewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:toInterfaceOrientation];
     }
    }
    
Maniac One
  • 579
  • 4
  • 23
1

I too was facing the same This was to fix my Camera orientation

override func shouldAutorotate() -> Bool {
        return false
    }

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
        return UIInterfaceOrientation.LandscapeLeft
    }

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.LandscapeLeft
    }

To fix the camera

let previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.avCaptureSession)    
previewLayer.frame = self.view.layer.frame
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
Cloy
  • 2,051
  • 18
  • 30
1

Maselko's answer nearly worked for me, except that if the status-bar-orientation flips then the camera output displays upside down. I've addressed that issue by re-calling Maselko's logic when the status bar flips.

Here's my modified Maselko solution (tested on ios12/swift4):

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    setCameraOrientation()
}

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

@objc func setCameraOrientation() {
    if let connection =  self.previewLayer?.connection  {
        let currentDevice: UIDevice = UIDevice.current
        let orientation: UIDeviceOrientation = currentDevice.orientation
        let previewLayerConnection : AVCaptureConnection = connection
        if previewLayerConnection.isVideoOrientationSupported {
            let o: AVCaptureVideoOrientation
            switch (orientation) {
            case .portrait: o = .portrait
            case .landscapeRight: o = .landscapeLeft
            case .landscapeLeft: o = .landscapeRight
            case .portraitUpsideDown: o = .portraitUpsideDown
            default: o = .portrait
            }

            previewLayerConnection.videoOrientation = o
            previewLayer!.frame = self.view.bounds
        }
    }
}
sttawm
  • 45
  • 7
1

Improved version of Maselko's answer

Works just great!

override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()
  if let connection =  previewView.connection  {
    if connection.isVideoOrientationSupported {
      let videoOrientation = AVCaptureVideoOrientation.init(rawValue: UIApplication.shared.statusBarOrientation.rawValue)!
      connection.videoOrientation = videoOrientation
      previewView.frame = self.view.bounds
    }
  }
}
Kenan Karakecili
  • 712
  • 5
  • 23
0

The only way it worked for me in iOS 8 to 11.1 without any problem is to do this and I should mention that in my case, I loaded my application only in landscape mode but it should work in all orientations.(Btw you can overlay the camera manually via imageview or anything u want this way very easily)

@interface ViewController (){
    AVCaptureVideoPreviewLayer * previewLayer;
    AVCaptureSession* session;
}
@property (weak, nonatomic) IBOutlet UIView *cameraPreviewView;


-(void)viewDidLoad{
    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] error:nil];
    if (captureInput) {
        session = [[AVCaptureSession alloc] init];
        [session addInput:captureInput];
    }
    previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
    [previewLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
    [previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];

    CALayer *rootLayer = [self.cameraPreviewView layer];
    [rootLayer setMasksToBounds:YES];
    [previewLayer setFrame:[self.view bounds]];
    [rootLayer addSublayer:previewLayer];
    [session startRunning];

    //Orientation Code is in viewDidLayoutSubviews method
}
-(void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    switch (orientation) {
        case UIInterfaceOrientationPortrait:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];
            break;
        case UIInterfaceOrientationLandscapeLeft:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
            break;
        case UIInterfaceOrientationLandscapeRight:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
            break;
        default:break;
    }
}
Reza.Ab
  • 945
  • 9
  • 19
0

@Maselko 's answer is correct but for one thing: You should be using UIApplication.shared.statusBarOrientation instead of UIDevice.current.orientation, because the device orientation is how your device is physically positioned. It breaks when your device is in landscape but your UI does not support that orientation (like when I was making a landscape-only camera app and initiate the view when the device is in portrait position).

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.current

        let orientation = UIApplication.shared.statusBarOrientation

        let previewLayerConnection : AVCaptureConnection = connection

        if previewLayerConnection.isVideoOrientationSupported {

            switch (orientation) {
            case .portrait: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)

                break

            case .landscapeRight: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeLeft)

                break

            case .landscapeLeft: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeRight)

                break

            case .portraitUpsideDown: updatePreviewLayer(layer: previewLayerConnection, orientation: .portraitUpsideDown)

                break

            default: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)

                break
            }
        }
    }
}
Teng L
  • 281
  • 3
  • 12
0

Here is the solution I'm using with Swift 4.

It's short and works beautifully for me.

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

    let videoLayer = self.previewLayer
    coordinator.animate(alongsideTransition: { (context: UIViewControllerTransitionCoordinatorContext) in

        guard let connection = videoLayer?.connection, connection.isVideoOrientationSupported, let orientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue) else {
            return
        }

        connection.videoOrientation = orientation
        videoLayer?.frame = self.view.bounds

    }) { (context: UIViewControllerTransitionCoordinatorContext) in
        // handle any completion logic here...
    }
}
digitalHound
  • 4,044
  • 22
  • 26