如何处理 AVCaptureVideoPreviewLayer 的设备旋转?

How to handle a device rotation for AVCaptureVideoPreviewLayer?

我有一个简单的相机预览实现:

import SwiftUI
import AVFoundation

struct CameraView: View {
    @StateObject var model = CameraModel()
    var body: some View {
        CameraPreview(camera: model)
            .safeAreaInset(edge: .bottom, alignment: .center, spacing: 0) {
                Color.clear
                    .frame(height: 0)
                    .background(Material.bar)
            }
            .ignoresSafeArea(.all, edges: .top)
            .onAppear() {
                model.check()
            }
    }
}

struct CameraPreview: UIViewRepresentable {
    @ObservedObject var camera: CameraModel
    
    func makeUIView(context: Context) -> some UIView {
        let view = UIView(frame: UIScreen.main.bounds)
        camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
        camera.preview.videoGravity = AVLayerVideoGravity.resizeAspectFill
        camera.preview.frame = view.frame
        view.layer.addSublayer(camera.preview)
        camera.start()
        return view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
    }
}

struct CameraView_Previews: PreviewProvider {
    static var previews: some View {
        CameraView()
    }
}

class CameraModel: ObservableObject {
    @Published var session = AVCaptureSession()
    @Published var alert = false
    @Published var preview: AVCaptureVideoPreviewLayer!
    
    func check() {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            setUp()
            break
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { (status) in
                if status {
                    self.setUp()
                }
            }
            break
        case .denied:
            self.alert.toggle()
            break
        default:
            break
        }
    }
    
    func setUp() {
        do {
            self.session.beginConfiguration()
            let device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back)
            let input = try AVCaptureDeviceInput(device: device!)
            
            if self.session.canAddInput(input) {
                self.session.addInput(input)
            }
            
            self.session.commitConfiguration()
        }
        catch {
            print(error.localizedDescription)
        }
    }
    
    func start() {
        self.session.startRunning()
    }
}

问题是它不处理屏幕旋转:

我找到了类似的主题,例如,this one,但我是 iOS 开发的菜鸟,我什至不知道把这个解决方案放在哪里。我检查过 ViewUIViewRepresentable 都没有这样的方法可以覆盖。

如何处理 AVCaptureVideoPreviewLayer 中的屏幕旋转?

如果要旋转更新图层框架,您需要创建自定义 UIView 并覆盖 layoutSubviews()。在 layoutSubviews() 内,您需要更新子图层的框架。

代码如下。

struct CameraPreview: UIViewRepresentable {
    @ObservedObject var camera: CameraModel
    
    class LayerView: UIView {
        override func layoutSubviews() {
            super.layoutSubviews()
            // To disable default animation of layer. You can comment out those lines with `CATransaction` if you want to include
            CATransaction.begin() 
            CATransaction.setDisableActions(true)
            layer.sublayers?.forEach({ layer in
                layer.frame = frame
            })
            CATransaction.commit()
        }
    }
    
    func makeUIView(context: Context) -> some UIView {
        let view = LayerView()
        camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
        camera.preview.frame = view.frame
        camera.preview.videoGravity = AVLayerVideoGravity.resizeAspectFill
        view.layer.addSublayer(camera.preview)
        camera.session.startRunning()
        return view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
    }
}

这是一个基于 :

的视频旋转的工作变体
struct CameraView: View {
    @StateObject var model = CameraModel()
    var body: some View {
        CameraPreview(camera: model)
            .safeAreaInset(edge: .bottom, alignment: .center, spacing: 0) {
                Color.clear
                    .frame(height: 0)
                    .background(Material.bar)
            }
            .ignoresSafeArea(.all, edges: [.top, .horizontal])
            .onAppear() {
                model.check()
            }
    }
}

struct CameraPreview: UIViewRepresentable {
    @ObservedObject var camera: CameraModel
    
    class LayerView: UIView {
        var parent: CameraPreview! = nil
        
        override func layoutSubviews() {
            super.layoutSubviews()
            // To disable default animation of layer. You can comment out those lines with `CATransaction` if you want to include
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            layer.sublayers?.forEach({ layer in
                layer.frame = UIScreen.main.bounds
            })
            self.parent.camera.rotate(orientation: UIDevice.current.orientation)
            CATransaction.commit()
        }
    }
    
    func makeUIView(context: Context) -> some UIView {
        let view = LayerView()
        view.parent = self
        camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
        camera.preview.videoGravity = AVLayerVideoGravity.resizeAspectFill
        camera.preview.frame = view.frame
        view.layer.addSublayer(camera.preview)
        camera.start()
        return view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
    }
}

struct CameraView_Previews: PreviewProvider {
    static var previews: some View {
        CameraView()
    }
}

class CameraModel: ObservableObject {
    @Published var session = AVCaptureSession()
    @Published var alert = false
    @Published var preview: AVCaptureVideoPreviewLayer!
    
    func check() {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            setUp()
            break
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { (status) in
                if status {
                    self.setUp()
                }
            }
            break
        case .denied:
            self.alert.toggle()
            break
        default:
            break
        }
    }
    
    func setUp() {
        do {
            self.session.beginConfiguration()
            let device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back)
            let input = try AVCaptureDeviceInput(device: device!)
            
            if self.session.canAddInput(input) {
                self.session.addInput(input)
            }
            
            self.session.commitConfiguration()
        }
        catch {
            print(error.localizedDescription)
        }
    }
    
    func start() {
        self.session.startRunning()
    }
    
    func rotate(orientation: UIDeviceOrientation) {
        let videoConnection = self.preview.connection
        switch orientation {
        case .portraitUpsideDown:
            videoConnection?.videoOrientation = .portraitUpsideDown
        case .landscapeLeft:
            videoConnection?.videoOrientation = .landscapeRight
        case .landscapeRight:
            videoConnection?.videoOrientation = .landscapeLeft
        case .faceDown:
            videoConnection?.videoOrientation = .portraitUpsideDown
        default:
            videoConnection?.videoOrientation = .portrait
        }
    }
}