防止 AVCaptureVideoPreviewLayer 旋转,但允许 UI 层随方向旋转

问题描述 投票:0回答:5

我有两个视图控制器。一种是根 VC,包含录制按钮等 UI 界面。在此视图控制器上,我还在索引 0 处显示另一个 VC 的视图。该视图包含 AVCaptureVideoPreviewLayer。

我希望我的摄像机能够模仿Apple摄像机应用程序,其中界面布局会随着旋转而调整,但视频预览层不会。您可以看到股票视频应用程序中的录制计时器 (UILabel) 如何根据方向消失并重新出现在顶部。

知道如何做到这一点吗?我发现一个建议建议将预览添加到应用程序委托的窗口,因为它不符合导航控制器的旋转,但它对我不起作用。

谢谢!

ios avfoundation avcapturesession avcapturedevice
5个回答
11
投票

我也有非常相似的情况。我只有一个视图控制器,我想要一个不会在其中旋转的

AVCaptureVideoPreviewLayer
。我发现 @SeanLintern88 接受的解决方案对我不起作用;状态栏从未移动,屏幕上的 WKWebView 无法正确调整大小。

我遇到的更大问题之一是我将

AVCaptureVideoPreviewLayer
放在视图控制器的视图中。最好创建一个新的
UIView
来保存图层。

之后我找到了Apple的技术说明QA1890:防止视图旋转。这使我能够快速生成以下代码:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    coordinator.animateAlongsideTransition(
        { (UIViewControllerTransitionCoordinatorContext) in
            let deltaTransform = coordinator.targetTransform()
            let deltaAngle = atan2f(Float(deltaTransform.b), Float(deltaTransform.a))
            var currentRotation : Float = (self.previewView!.layer.valueForKeyPath("transform.rotation.z")?.floatValue)!
            // Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
            currentRotation += -1 * deltaAngle + 0.0001;
            self.previewView!.layer.setValue(currentRotation, forKeyPath: "transform.rotation.z")
            self.previewView!.layer.frame = self.view.bounds
        },
        completion:
        { (UIViewControllerTransitionCoordinatorContext) in
            // Integralize the transform to undo the extra 0.0001 added to the rotation angle.
            var currentTransform : CGAffineTransform = self.previewView!.transform
            currentTransform.a = round(currentTransform.a)
            currentTransform.b = round(currentTransform.b)
            currentTransform.c = round(currentTransform.c)
            currentTransform.d = round(currentTransform.d)
            self.previewView!.transform = currentTransform
        })
}

最初的技术说明没有

self.previewView!.layer.frame = self.view.bounds
线,但我发现这是非常必要的,因为虽然锚点没有移动,但框架却移动了。如果没有该线,预览将会偏移。

此外,由于我正在做所有工作以将视图保持在正确的位置,因此我必须删除其上的所有定位约束。当我放入它们时,它们会导致预览向相反方向偏移。


6
投票

确保将 shouldAutorotate 设置为返回 false:

-(BOOL)shouldAutorotate{
    return NO;
}

注册方向更改的通知:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(orientationChanged:)
                                             name:UIDeviceOrientationDidChangeNotification
                                           object:nil];

实施通知变更

-(void)orientationChanged:(NSNotification *)notif {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];

// Calculate rotation angle
CGFloat angle;
switch (deviceOrientation) {
    case UIDeviceOrientationPortraitUpsideDown:
        angle = M_PI;
        break;
    case UIDeviceOrientationLandscapeLeft:
        angle = M_PI_2;
        break;
    case UIDeviceOrientationLandscapeRight:
        angle = - M_PI_2;
        break;
    default:
        angle = 0;
        break;
}


}

并旋转 UI

 [UIView animateWithDuration:.3 animations:^{
        self.closeButton.transform = CGAffineTransformMakeRotation(angle);
        self.gridButton.transform = CGAffineTransformMakeRotation(angle);
        self.flashButton.transform = CGAffineTransformMakeRotation(angle);
    } completion:^(BOOL finished) {

}];

这就是我实现屏幕被锁定但旋转 UI 的方法,如果这有效,请链接堆栈帖子,我可以将其复制到那里,您可以勾选它:P


1
投票

您会发现相机应用程序仅支持纵向方向并根据需要旋转视图元素。

我来到这里遇到了类似的问题,只是我最终要求

AVCaptureVideoPreviewLayer
与视图的其余部分保持方向,以便我可以正确使用
metadataOutputRectConverted(fromLayerRect:)
方法。

就像 Erik Allens 上面的回答一样,我的解决方案基于 技术问答 QA1890 防止视图旋转,但已更新到 Swift 5,并在界面转换完成后删除旋转变换。

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        self.cameraPreviewView = UIView(frame: view.bounds)
        self.cameraPreviewView.layer.addSublayer(self.videoPreviewLayer)
        self.videoPreviewLayer.connection?.videoOrientation = UIApplication.shared.statusBarOrientation.asAVCaptureVideoOrientation()
        self.videoPreviewLayer.frame = self.cameraPreviewView.bounds
        self.view.addSubview(self.cameraPreviewView)
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        self.cameraPreviewView.center = self.view.bounds.midPoint
    }

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

        let cameraPreviewTransform = self.cameraPreviewView.transform

        coordinator.animate(alongsideTransition: { context in
            // Keep the camera preview view inversely rotatated with the rest of the view during transition animations
            let deltaTransform = coordinator.targetTransform
            let deltaAngle: CGFloat = atan2(deltaTransform.b, deltaTransform.a)

            var previewCurrentRotation = atan2(cameraPreviewTransform.b, cameraPreviewTransform.a)

            // Adding a small value to the rotation angle forces the animation to occur in a the desired direction,
            // preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
            previewCurrentRotation += -1 * deltaAngle + 0.0001
            self.cameraPreviewView.layer.setValue(previewCurrentRotation, forKeyPath: "transform.rotation.z")
        }, completion: { context in
            // Now the view transition animations are complete, we will adjust videoPreviewLayer properties to fit the current orientation
            // Changing the frame of a videoPreviewLayer animates the resizing of the preview view, so we disable animations (actions) to remove this effect
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            self.cameraPreviewView.transform = cameraPreviewTransform
            self.cameraPreviewView.frame = self.view.bounds

            self.videoPreviewLayer.connection?.videoOrientation = UIApplication.shared.statusBarOrientation.asAVCaptureVideoOrientation()
            self.videoPreviewLayer.frame = self.cameraPreviewView.bounds

            CATransaction.commit()
        })
    }

extension UIInterfaceOrientation {
    func asAVCaptureVideoOrientation() -> AVCaptureVideoOrientation {
        switch self {
        case .portrait:
            return .portrait
        case .landscapeLeft:
            return .landscapeLeft
        case .landscapeRight:
            return .landscapeRight
        case .portraitUpsideDown:
            return .portraitUpsideDown
        default:
            return .portrait
        }
    }
}

0
投票

在显示相机输出的视图上添加:

AVCaptureVideoPreviewLayer *layer = (AVCaptureVideoPreviewLayer *)self.layer;
if ([layer.connection isVideoOrientationSupported]) {
    [layer.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
  }

AVCaptureVideoOrientationPortrait 只是一种选择。您可以选择以下选项:

typedef NS_ENUM(NSInteger, AVCaptureVideoOrientation) {
    AVCaptureVideoOrientationPortrait           = 1,
    AVCaptureVideoOrientationPortraitUpsideDown = 2,
    AVCaptureVideoOrientationLandscapeRight     = 3,
    AVCaptureVideoOrientationLandscapeLeft      = 4,
}

必须在设置会话后完成此操作。


0
投票
现在是 2024 年,我在 SwiftUI 上遇到了这个问题,目标是 iOS 16 和最新的开发工具/Swift 版本。

我必须将这个问题中的所有提案拼凑起来,并将其范围缩小到有效的范围。就是这个。终于。

我的应用程序的所有其他部分都随设备旋转,而预览层看起来静止。我从 UIViewControllerRepresentable 中使用它,我直接在 SwiftUI 中使用它。

顺便说一句,当在landscapeLeft 和landscapeRight 之间任意方向发生变化时,0.0001 hack 是绝对必要的。没有它,纵向到横向的过渡仍然有效。

public class ViewfinderController: UIViewController { public var previewLayer: AVCaptureVideoPreviewLayer init(previewLayer: AVCaptureVideoPreviewLayer) { self.previewLayer = previewLayer super.init(nibName: nil, bundle: nil) self.view.layer.addSublayer(self.previewLayer) } @available(*, unavailable, message: "No nibs for you!") public required init?(coder aDecoder: NSCoder) { fatalError("Nibs are not supported here.") } public override func viewDidLoad() { super.viewDidLoad() self.view.layer.addSublayer(previewLayer) } public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() previewLayer.frame = view.bounds } public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) // This is the magic to keep the preview layer fixed during device rotations. coordinator.animate(alongsideTransition: { [weak self] context in guard let strongSelf = self else { return } let deltaTransform = coordinator.targetTransform let deltaAngle: CGFloat = atan2(deltaTransform.b, deltaTransform.a) let currentRotation = atan2(strongSelf.view.transform.b, strongSelf.view.transform.a) // Adding a small value to the rotation angle forces the animation to occur in the desired direction, // preventing an issue where the view would appear to rotate 2pi radians during any rotation between landscapeLeft and landscapeRight. let newRotation = currentRotation - deltaAngle + 0.0001 strongSelf.view.transform = CGAffineTransform(rotationAngle: newRotation) strongSelf.view.frame = CGRect(origin: .zero, size: size) }) } }
    
© www.soinside.com 2019 - 2024. All rights reserved.