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

Preventing AVCaptureVideoPreviewLayer from rotating, but allow UI layer to rotate with orientation

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

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

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

谢谢!

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

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

这必须在您设置会话后完成。

确保将 shouldAutorotate 设置为 return 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 的方式,如果可行 link 堆栈 post 我可以将其复制到那里,你可以勾选它:P

我的情况非常相似。我只有一个视图控制器,我想要一个不在其中旋转的 AVCaptureVideoPreviewLayer。我发现@SeanLintern88 接受的解决方案对我不起作用;状态栏从未移动,屏幕上的 WKWebView 未正确调整大小。

我 运行 遇到的一个更大的问题是我将 AVCaptureVideoPreviewLayer 放在视图控制器的视图中。最好创建一个新的 UIView 来容纳图层。

之后我找到了 Apple QA1890: Preventing a View From Rotating 的技术说明。这使我能够生成以下 swift 代码:

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 行,但我发现这非常必要,因为虽然锚点没有移动,但框架移动了。如果没有那条线,预览将会偏移。

此外,由于我所做的所有工作都是将视图保持在正确的位置,因此我不得不删除对它的所有定位约束。当我加入它们时,它们会导致预览向相反的方向偏移。

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

我来到这里时遇到了类似的问题,只是我最终要求 AVCaptureVideoPreviewLayer 与视图的其余部分保持一致,这样我才能正确使用 metadataOutputRectConverted(fromLayerRect:) 方法。

就像上面 Erik Allens 的回答一样,我的解决方案基于 Technical Q&A QA1890 Preventing a View From Rotating,但已更新为 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
        }
    }
}