AVCaptureVideoPreviewLayer альбомная ориентация

Как я могу заставить «AVCaptureVideoPreviewLayer» правильно отображаться в альбомной ориентации? Он отлично работает в портретной ориентации, но не вращается и показывает повернутый снимок камеры, когда родительский контроллер представления находится в альбомной ориентации.


person Ege Akpinar    schedule 21.01.2014    source источник


Ответы (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 в альбомную ориентацию.

  1. Захват будет выглядеть повернутым (как будто вы держите камеру на 90 градусов).
  2. Он не будет полностью охватывать установленные границы

Проблема здесь в том, что «CALayer» не поддерживает авторотацию, поэтому, в отличие от «UIView», который вы добавляете в качестве подпредставления, он не будет вращаться, когда вращается его родительский «UIView». Таким образом, вы должны вручную обновлять его фрейм каждый раз, когда изменяются границы родительского представления (не фрейм родительского представления, поскольку фрейм остается неизменным после поворота). Это достигается путем переопределения 'viewWillLayoutSubviews' в контроллере представления контейнера.

Во-вторых, вы должны использовать свойство «videoOrientation», чтобы сообщить AVFoundation об ориентации, чтобы он правильно выполнил предварительный просмотр.

Надеюсь это поможет.

person Ege Akpinar    schedule 21.01.2014
comment
Здорово ! Но мне пришлось реализовать will- или didRotateToInterfaceOrientation() с вызовом self.view.setNeedsLayout(), чтобы вызывать viewWillLayoutSubviews - person Jan ATAC; 28.01.2016
comment
Этот метод мне очень помог. Спасибо, приятель. - person dead_soldier; 29.02.2016
comment
Использование viewWillLayoutSubviews() иногда дает сбой, когда вы поворачиваете устройство несколько раз (особенно повороты на 180 °). Реализация того же поведения в willRotateToInterfaceOrientation() лучше решила проблему для меня. Спасибо, в любом случае ;) - person Tib; 17.08.2016

Свифт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
        }
    }
}
person neoneye    schedule 04.12.2016
comment
Идеально. Спасибо! - person David Vincent Gagne; 20.07.2017

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 КОНВЕРСИЯ

person Jorge L. Jaramillo    schedule 10.10.2016
comment
Это работает отлично, за исключением случаев, когда камера не отображается и не работает с помощью viewDidLoad(), что приводит к всевозможным проблемам, поскольку предварительный слой может еще не существовать. Из-за этого ответ @neoneye выше следует считать правильным. - person David Vincent Gagne; 20.07.2017

Свифт 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
        }
    }
}

person Jack Sanford    schedule 22.06.2020

Помимо viewWillLayoutSubviews(), вам также необходимо реализовать didRotateFromInterfaceOrientation().

Это связано с тем, что макеты подпредставления не будут обновляться, если устройство переключается из портретного режима в портретный перевернутый режим (очевидно, из соображений эффективности).

person waldgeist    schedule 05.08.2015

Ответы Джексанфорда и NeonEye великолепны. Тем не менее, у меня есть быстрое предложение.

Поскольку statusBarOrientation устарела в iOS 13, в итоге я использовал

connection.videoOrientation = self.interfaceOrientation(toVideoOrientation: UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .portrait

person azwethinkweiz    schedule 01.08.2020

Недостаточно просто изменить ориентацию соединения 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
    }
}
person Hopreeeenjust    schedule 14.08.2020