【问题标题】:Preventing AVCaptureVideoPreviewLayer from rotating, but allow UI layer to rotate with orientation防止 AVCaptureVideoPreviewLayer 旋转,但允许 UI 层随方向旋转
【发布时间】:2015-05-12 22:33:53
【问题描述】:

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

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

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

谢谢!

【问题讨论】:

    标签: ios avfoundation avcapturesession avcapturedevice


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 如果您创建CGAffineTransform(rotationAngle: currentRotation - deltaAngle) 并将其设置在具有捕获层的视图的变换属性上(我使用UIView 子类与layerClass override),然后更新视图框架。然后可以删除完成闭包。
    【解决方案2】:

    确保将 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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-09-25
          • 2022-01-15
          • 2023-04-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-08
          相关资源
          最近更新 更多