Как я могу заставить «AVCaptureVideoPreviewLayer» правильно отображаться в альбомной ориентации? Он отлично работает в портретной ориентации, но не вращается и показывает повернутый снимок камеры, когда родительский контроллер представления находится в альбомной ориентации.
AVCaptureVideoPreviewLayer альбомная ориентация
Ответы (7)
Во-первых, ответ
- (void)viewWillLayoutSubviews {
_captureVideoPreviewLayer.frame = self.view.bounds;
if (_captureVideoPreviewLayer.connection.supportsVideoOrientation) {
_captureVideoPreviewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
}
}
- (AVCaptureVideoOrientation)interfaceOrientationToVideoOrientation:(UIInterfaceOrientation)orientation {
switch (orientation) {
case UIInterfaceOrientationPortrait:
return AVCaptureVideoOrientationPortrait;
case UIInterfaceOrientationPortraitUpsideDown:
return AVCaptureVideoOrientationPortraitUpsideDown;
case UIInterfaceOrientationLandscapeLeft:
return AVCaptureVideoOrientationLandscapeLeft;
case UIInterfaceOrientationLandscapeRight:
return AVCaptureVideoOrientationLandscapeRight;
default:
break;
}
NSLog(@"Warning - Didn't recognise interface orientation (%d)",orientation);
return AVCaptureVideoOrientationPortrait;
}
Я столкнулся с несколькими сообщениями SO по этому вопросу и не смог найти простого объяснения, поэтому решил поделиться своим.
Если вы будете следовать примеру Apple в этом вопросе, вы столкнетесь с двумя потенциальными проблемами при повороте устройства iOS в альбомную ориентацию.
- Захват будет выглядеть повернутым (как будто вы держите камеру на 90 градусов).
- Он не будет полностью охватывать установленные границы
Проблема здесь в том, что «CALayer» не поддерживает авторотацию, поэтому, в отличие от «UIView», который вы добавляете в качестве подпредставления, он не будет вращаться, когда вращается его родительский «UIView». Таким образом, вы должны вручную обновлять его фрейм каждый раз, когда изменяются границы родительского представления (не фрейм родительского представления, поскольку фрейм остается неизменным после поворота). Это достигается путем переопределения 'viewWillLayoutSubviews' в контроллере представления контейнера.
Во-вторых, вы должны использовать свойство «videoOrientation», чтобы сообщить AVFoundation об ориентации, чтобы он правильно выполнил предварительный просмотр.
Надеюсь это поможет.
Свифт3
func updateVideoOrientation() {
guard let previewLayer = self.previewLayer else {
return
}
guard previewLayer.connection.isVideoOrientationSupported else {
print("isVideoOrientationSupported is false")
return
}
let statusBarOrientation = UIApplication.shared.statusBarOrientation
let videoOrientation: AVCaptureVideoOrientation = statusBarOrientation.videoOrientation ?? .portrait
if previewLayer.connection.videoOrientation == videoOrientation {
print("no change to videoOrientation")
return
}
previewLayer.frame = cameraView.bounds
previewLayer.connection.videoOrientation = videoOrientation
previewLayer.removeAllAnimations()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil, completion: { [weak self] (context) in
DispatchQueue.main.async(execute: {
self?.updateVideoOrientation()
})
})
}
extension UIInterfaceOrientation {
var videoOrientation: AVCaptureVideoOrientation? {
switch self {
case .portraitUpsideDown: return .portraitUpsideDown
case .landscapeRight: return .landscapeRight
case .landscapeLeft: return .landscapeLeft
case .portrait: return .portrait
default: return nil
}
}
}
override func viewWillLayoutSubviews() {
self.previewLayer.frame = self.view.bounds
if previewLayer.connection.isVideoOrientationSupported {
self.previewLayer.connection.videoOrientation = self.interfaceOrientation(toVideoOrientation: UIApplication.shared.statusBarOrientation)
}
}
func interfaceOrientation(toVideoOrientation orientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
switch orientation {
case .portrait:
return .portrait
case .portraitUpsideDown:
return .portraitUpsideDown
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
default:
break
}
print("Warning - Didn't recognise interface orientation (\(orientation))")
return .portrait
}
//SWIFT 3 КОНВЕРСИЯ
Свифт 5 (iOS 13.0+):
func updateVideoOrientation() {
guard let videoPreviewLayer = self.videoPreviewLayer else {
return
}
guard videoPreviewLayer.connection!.isVideoOrientationSupported else {
print("isVideoOrientationSupported is false")
return
}
let statusBarOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
let videoOrientation: AVCaptureVideoOrientation = statusBarOrientation?.videoOrientation ?? .portrait
videoPreviewLayer.frame = view.layer.bounds
videoPreviewLayer.connection?.videoOrientation = videoOrientation
videoPreviewLayer.removeAllAnimations()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil, completion: { [weak self] (context) in
DispatchQueue.main.async(execute: {
self?.updateVideoOrientation()
})
})
}
extension UIInterfaceOrientation {
var videoOrientation: AVCaptureVideoOrientation? {
switch self {
case .portraitUpsideDown: return .portraitUpsideDown
case .landscapeRight: return .landscapeRight
case .landscapeLeft: return .landscapeLeft
case .portrait: return .portrait
default: return nil
}
}
}
Помимо viewWillLayoutSubviews(), вам также необходимо реализовать didRotateFromInterfaceOrientation().
Это связано с тем, что макеты подпредставления не будут обновляться, если устройство переключается из портретного режима в портретный перевернутый режим (очевидно, из соображений эффективности).
Ответы Джексанфорда и NeonEye великолепны. Тем не менее, у меня есть быстрое предложение.
Поскольку statusBarOrientation
устарела в iOS 13, в итоге я использовал
connection.videoOrientation = self.interfaceOrientation(toVideoOrientation: UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .portrait
Недостаточно просто изменить ориентацию соединения AVCaptureVideoPreviewLayer. Это обновит только изображение, которое вы видите на экране.
Но фактическое изображение останется прежним. Вы можете распечатать размер выходного изображения в разных ориентациях и проверить результаты.
Чтобы вращение действительно работало, вам также необходимо изменить ориентацию соединения AVCaptureSession.
func updateVideoOrientation() {
if let previewLayerConnection = previewLayer.connection, previewLayerConnection.isVideoOrientationSupported {
previewLayerConnection.videoOrientation = currentVideoOrientation
}
if let captureSessionConnection = captureSession.connections.first, captureSessionConnection.isVideoOrientationSupported {
captureSessionConnection.videoOrientation = currentVideoOrientation
}
}