防止 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
}
}
}
我有两个视图控制器。一个是根 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
}
}
}