ios - 如何处理 AVCaptureVideoPreviewLayer 的设备旋转?

标签 ios swiftui avfoundation screen-orientation

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

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()
    }
}

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

Screenshot

我找到了类似的主题,例如 this one ,但我是iOS开发的菜鸟,我什至不明白该解决方案放在哪里。我检查过 ViewUIViewRepresentable 都没有要重写的此类方法。

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

最佳答案

这是一个基于 Dscyre Scotti'es answer 的视频旋转工作变体:

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
        }
    }
}

关于ios - 如何处理 AVCaptureVideoPreviewLayer 的设备旋转?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71700250/

相关文章:

swift - 从 AVCaptureSession 录制的音频在流式传输到另一台设备时生成 "Bad Address"错误

iphone - 有什么方法可以使用AVCaptureStillImageOutput改善两次拍摄之间的时间吗?

ios - 从另一个 viewController Swift 中删除通知观察者

ios - iOS 应用上 Web Trends SDK 的谷歌移动广告问题

ios - SwiftUI - 禁用某些 View 从右到左语言翻转

swift - 如何使用 SwiftUI 中的按钮从数组中删除项目?

swift - 如何控制 SwiftUI 图像的无障碍画外音文本

audio - Swift- 音频实现崩溃 : "EXC_BAD_INSTRUCTION"

android - 在 Unity Android/iOS 中高效存储大量图像

ios - swift 3 firebase 快照如果值等于...获取