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

后端 未结 4 650
半阙折子戏
半阙折子戏 2020-12-16 07:26

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 in

相关标签:
4条回答
  • 2020-12-16 07:37

    You'll find that the Camera app only supports Portrait orientation and rotates view elements as required.

    I came here with a similar issue, except I ended up requiring the AVCaptureVideoPreviewLayer to stay oriented with the rest of the view so i could correctly use the metadataOutputRectConverted(fromLayerRect:) method.

    Like Erik Allens answer above, My solution is based off Technical Q&A QA1890 Preventing a View From Rotating, but has been updated to Swift 5 and removes the rotation transform once the interface transition is complete.

        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 讨论(0)
  • 2020-12-16 07:41

    On the view that shows the camera output add:

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

    AVCaptureVideoOrientationPortrait is just one option. You can choose from the following:

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

    This must be done after you setup the session.

    0 讨论(0)
  • 2020-12-16 07:48

    Make sure to set shouldAutorotate to return false:

    -(BOOL)shouldAutorotate{
        return NO;
    }
    

    register for Notifications that orientation changed:

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

    implement the notification change

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

    and rotate the UI

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

    This is how I implement the screen being locked but rotating the UI, if this works link the stacks post and I can copy it over there and you can tick it :P

    0 讨论(0)
  • 2020-12-16 07:58

    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.

    0 讨论(0)
提交回复
热议问题