You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确使用RotationCoordinator及其videoRotationAngleForHorizonLevelPreview设置相机预览方向

如何正确使用RotationCoordinator及其videoRotationAngleForHorizonLevelPreview设置相机预览方向

我明白你现在在SwiftUI里用新的RotationCoordinator替代废弃的AVCaptureConnection.videoOrientation时遇到了方向不对的问题,我之前也踩过这个坑,给你梳理下正确的用法:

首先得明确,RotationCoordinator是和你的AVCaptureDevice绑定的,它的videoRotationAngleForHorizonLevelPreview()方法会返回一个让预览画面保持水平的旋转角度,我们需要把这个角度应用到AVCaptureVideoPreviewLayer的变换上,而不是依赖旧的属性。

第一步:确保RotationCoordinator和捕获设备正确绑定

在初始化相机设备的时候,直接从当前设备创建RotationCoordinator,不要随便传一个无关的实例:

// 示例:相机初始化时创建rotationCoordinator
guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
    fatalError("无法获取后置相机")
}
let rotationCoordinator = captureDevice.rotationCoordinator

第二步:修改CameraPreviewView,添加旋转更新逻辑

原来的CameraPreviewView只做了初始化和帧更新,缺少了旋转角度的应用和方向变化监听。下面是修正后的完整代码:

struct CameraPreview: UIViewRepresentable {
    let previewLayer: AVCaptureVideoPreviewLayer
    let rotationCoordinator: AVCaptureDevice.RotationCoordinator

    func makeUIView(context: Context) -> CameraPreviewView {
        let view = CameraPreviewView(previewLayer: previewLayer, rotationCoordinator: rotationCoordinator)
        // 初始化时就设置一次旋转
        view.updatePreviewRotation()
        return view
    }

    func updateUIView(_ uiView: CameraPreviewView, context: Context) {
        uiView.updatePreviewLayerFrame()
        // 尺寸变化后同步更新旋转
        uiView.updatePreviewRotation()
    }
}

class CameraPreviewView: UIView {
    private let previewLayer: AVCaptureVideoPreviewLayer
    private let rotationCoordinator: AVCaptureDevice.RotationCoordinator
    private var orientationObserver: NSObjectProtocol?

    init(previewLayer: AVCaptureVideoPreviewLayer, rotationCoordinator: AVCaptureDevice.RotationCoordinator) {
        self.previewLayer = previewLayer
        self.rotationCoordinator = rotationCoordinator
        super.init(frame: .zero)
        setupPreviewLayer()
        setupOrientationObserver()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupPreviewLayer() {
        previewLayer.frame = bounds
        previewLayer.videoGravity = .resizeAspectFill
        layer.addSublayer(previewLayer)
    }

    private func setupOrientationObserver() {
        // 监听设备方向变化,实时更新预览旋转
        orientationObserver = NotificationCenter.default.addObserver(
            forName: UIDevice.orientationDidChangeNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.updatePreviewRotation()
        }
        // 开启设备方向监听
        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
    }

    func updatePreviewLayerFrame() {
        previewLayer.frame = bounds
    }

    func updatePreviewRotation() {
        // 获取让预览保持水平的旋转角度
        let rotationAngle = rotationCoordinator.videoRotationAngleForHorizonLevelPreview()
        // 将角度应用到预览层的变换上
        previewLayer.setAffineTransform(CGAffineTransform(rotationAngle: rotationAngle))
    }

    deinit {
        // 移除监听,避免内存泄漏
        if let observer = orientationObserver {
            NotificationCenter.default.removeObserver(observer)
        }
        UIDevice.current.endGeneratingDeviceOrientationNotifications()
    }
}

关键注意点:

  • 一定要把videoRotationAngleForHorizonLevelPreview()返回的角度通过CGAffineTransform应用到previewLayer的transform上,这是新API的核心用法,替代了旧的videoOrientation设置。
  • 必须监听UIDevice.orientationDidChangeNotification,因为设备方向变化时,rotationCoordinator返回的角度会改变,需要实时更新预览层的变换。
  • 在updateUIView里也要调用updatePreviewRotation(),因为SwiftUI的视图尺寸变化时,预览层的帧更新后可能需要重新调整旋转。

这样调整后,你的相机预览应该就能正确跟随设备方向,保持水平显示了。

备注:内容来源于stack exchange,提问作者user924

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 14:48:02