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

时间:2015-05-12 22:33:53

标签: ios avfoundation avcapturesession avcapturedevice

I have two view controllers. One is the root VC and contains the UI interface such as the record button. On this view controller, I also display the view of another VC at index 0. This view contains a AVCaptureVideoPreviewLayer.

I would like my video camera to mimic the Apple video camera app, where the interface layout adjusts with the rotation, but the video preview layer does not. You can see how the recording timer (UILabel) in the stock video app disappears and reappears at the top depending on the orientation.

Any idea how to do this? I found one suggestion that recommendeds adding the preview to the app delegate's window, since it won't conform to the rotation of the nav controller, but it didn't work for me.

Thanks!

4 个答案:

答案 0 :(得分:7)

I have a very similar situation. I just have one view controller and I want to have a AVCaptureVideoPreviewLayer that doesn't rotate in it. I found the accepted solution by @SeanLintern88 did not work for me; the status bar never moved and the WKWebView I had on the screen was not getting resizes properly.

One of the bigger issues I ran into was that I was putting my AVCaptureVideoPreviewLayer in the view controller's view. It is much better to create a new UIView just to hold the layer.

After that I found a technical note from Apple QA1890: Preventing a View From Rotating. This allowed me to produce the following swift code:

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

The original tech note did not have the line self.previewView!.layer.frame = self.view.bounds but I found that very necessary because although the anchor point doesn't move, the frame has. Without that line, the preview will be offset.

Also, since I am doing all of the work keeping the view in the correct position, I had to remove all the positioning constraints on it. When I had them in, they would cause the preview to instead be offset in the opposite direction.

答案 1 :(得分: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;
}


}

并旋转用户界面

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

}];

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:0)

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

我来这里遇到了类似的问题,只是我最终要求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
        }
    }
}