AVCaptureVideoPreviewLayer横向方向预览

19

如何使'AVCaptureVideoPreviewLayer'在横屏方向下正确显示?它在竖屏方向下工作正常,但不会旋转,并且当其父视图控制器处于横屏方向时,显示的是旋转后的相机捕捉。

7个回答

35

首先,这是答案。

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

我在Stack Overflow上看到了几篇关于这个问题的文章,但没有找到任何简单的解释,因此我想分享一下我的经验。

如果你按照苹果公司的示例进行操作,在将iOS设备旋转为横向时可能会遇到两个潜在问题:

  1. 捕获的画面会出现旋转(就好像你把相机旋转了90度)
  2. 它不会完全跨越其设置的边界

问题在于'CALayer'不支持自动旋转,因此与作为子视图添加的'UIView'不同,当其父'UIView'旋转时,它不会旋转。因此,每次父视图的边界发生更改时(不是父视图的框架,因为框架在旋转后保持不变),必须手动更新其框架。这可以通过在容器视图控制器中覆盖“viewWillLayoutSubviews”来实现。

其次,您应该使用“videoOrientation”属性通知AVFoundation有关方向,以便正确预览。

希望这能帮助到你。


太好了!但我不得不实现will-或didRotateToInterfaceOrientation(),并调用self.view.setNeedsLayout()才能使viewWillLayoutSubviews被调用。 - Jan ATAC
这个方法帮了我很大的忙。谢谢啊伙计。 - dead_soldier
有时候使用viewWillLayoutSubviews()在多次旋转设备时会出现问题(尤其是180°旋转)。对我来说,在willRotateToInterfaceOrientation()中实现相同的行为更好地解决了这个问题。无论如何,还是谢谢;) - Tib

10

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
        }
    }
}

完美。谢谢! - David Vincent Gagne

9

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
        }
    }
}


6
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 转换


这段代码在大多数情况下都能完美运行,但是当相机没有被显示并且在viewDidLoad()中运行时,就会出现各种问题,因为预览层可能还不存在。因此,@neoneye上面的答案应该被认为是正确的。 - David Vincent Gagne

2

仅仅更改AVCaptureVideoPreviewLayer的连接方向是不够的。

这只会更新您在屏幕上看到的图像。

但实际图像仍然保持不变。您可以在不同方向上打印输出图像大小并检查结果。

要使旋转真正起作用,您还需要更改AVCaptureSession的连接方向。

func updateVideoOrientation() {
    if let previewLayerConnection = previewLayer.connection, previewLayerConnection.isVideoOrientationSupported {
        previewLayerConnection.videoOrientation = currentVideoOrientation
    }
    if let captureSessionConnection = captureSession.connections.first, captureSessionConnection.isVideoOrientationSupported {
        captureSessionConnection.videoOrientation = currentVideoOrientation
    }
}

1
Jacksanford和NeonEye的回答很好。不过,我有一个快速建议。 由于在iOS 13中statusBarOrientation已被弃用,因此我最终使用了connection.videoOrientation = self.interfaceOrientation(toVideoOrientation: UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .portrait

0
除了实现viewWillLayoutSubviews()之外,您还需要实现didRotateFromInterfaceOrientation()。
这是因为如果设备从纵向模式旋转到倒置的纵向模式(显然是为了提高效率),子视图布局不会被更新。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接