我有两个视图控制器。一种是根 VC,包含录制按钮等 UI 界面。在此视图控制器上,我还在索引 0 处显示另一个 VC 的视图。该视图包含 AVCaptureVideoPreviewLayer。
我希望我的摄像机能够模仿Apple摄像机应用程序,其中界面布局会随着旋转而调整,但视频预览层不会。您可以看到股票视频应用程序中的录制计时器 (UILabel) 如何根据方向消失并重新出现在顶部。
知道如何做到这一点吗?我发现一个建议建议将预览添加到应用程序委托的窗口,因为它不符合导航控制器的旋转,但它对我不起作用。
谢谢!
我也有非常相似的情况。我只有一个视图控制器,我想要一个不会在其中旋转的
AVCaptureVideoPreviewLayer
。我发现 @SeanLintern88 接受的解决方案对我不起作用;状态栏从未移动,屏幕上的 WKWebView 无法正确调整大小。
我遇到的更大问题之一是我将
AVCaptureVideoPreviewLayer
放在视图控制器的视图中。最好创建一个新的 UIView
来保存图层。
之后我找到了Apple的技术说明QA1890:防止视图旋转。这使我能够快速生成以下代码:
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
线,但我发现这是非常必要的,因为虽然锚点没有移动,但框架却移动了。如果没有该线,预览将会偏移。
此外,由于我正在做所有工作以将视图保持在正确的位置,因此我必须删除其上的所有定位约束。当我放入它们时,它们会导致预览向相反方向偏移。
确保将 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
您会发现相机应用程序仅支持纵向方向并根据需要旋转视图元素。
我来到这里遇到了类似的问题,只是我最终要求
AVCaptureVideoPreviewLayer
与视图的其余部分保持方向,以便我可以正确使用 metadataOutputRectConverted(fromLayerRect:)
方法。
就像 Erik Allens 上面的回答一样,我的解决方案基于 技术问答 QA1890 防止视图旋转,但已更新到 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
}
}
}
在显示相机输出的视图上添加:
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,
}
必须在设置会话后完成此操作。
我必须将这个问题中的所有提案拼凑起来,并将其范围缩小到有效的范围。就是这个。终于。
我的应用程序的所有其他部分都随设备旋转,而预览层看起来静止。我从 UIViewControllerRepresentable 中使用它,我直接在 SwiftUI 中使用它。
顺便说一句,当在landscapeLeft 和landscapeRight 之间任意方向发生变化时,0.0001 hack 是绝对必要的。没有它,纵向到横向的过渡仍然有效。
public class ViewfinderController: UIViewController {
public var previewLayer: AVCaptureVideoPreviewLayer
init(previewLayer: AVCaptureVideoPreviewLayer) {
self.previewLayer = previewLayer
super.init(nibName: nil, bundle: nil)
self.view.layer.addSublayer(self.previewLayer)
}
@available(*, unavailable, message: "No nibs for you!")
public required init?(coder aDecoder: NSCoder) {
fatalError("Nibs are not supported here.")
}
public override func viewDidLoad() {
super.viewDidLoad()
self.view.layer.addSublayer(previewLayer)
}
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
previewLayer.frame = view.bounds
}
public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// This is the magic to keep the preview layer fixed during device rotations.
coordinator.animate(alongsideTransition: { [weak self] context in
guard let strongSelf = self else { return }
let deltaTransform = coordinator.targetTransform
let deltaAngle: CGFloat = atan2(deltaTransform.b, deltaTransform.a)
let currentRotation = atan2(strongSelf.view.transform.b, strongSelf.view.transform.a)
// Adding a small value to the rotation angle forces the animation to occur in the desired direction,
// preventing an issue where the view would appear to rotate 2pi radians during any rotation between landscapeLeft and landscapeRight.
let newRotation = currentRotation - deltaAngle + 0.0001
strongSelf.view.transform = CGAffineTransform(rotationAngle: newRotation)
strongSelf.view.frame = CGRect(origin: .zero, size: size)
})
}
}