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

如何让UIViewRepresentable中的代码块仅执行一次?

解决方案

方法一:用Coordinator管理可变状态

UIViewRepresentable的Coordinator是类类型,可安全保存可变状态,用来标记预览层是否已添加,确保逻辑仅执行一次。

修改后的VideoPreviewView代码:

struct VideoPreviewView: UIViewRepresentable {
    @EnvironmentObject var cameraManager: CameraManager
    
    class Coordinator: NSObject {
        var hasAddedPreviewLayer = false
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    func makeUIView(context: Context) -> UIView {
        UIView()
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        guard cameraManager.isSessionInitialized, !context.coordinator.hasAddedPreviewLayer else {
            return
        }
        
        let previewLayer = AVCaptureVideoPreviewLayer(session: cameraManager.captureSession)
        previewLayer.frame = uiView.layer.bounds
        previewLayer.videoGravity = .resizeAspectFill
        uiView.layer.addSublayer(previewLayer)
        
        context.coordinator.hasAddedPreviewLayer = true
        
        // 监听视图尺寸变化,自动更新预览层frame
        NotificationCenter.default.addObserver(
            forName: UIView.boundsDidChangeNotification,
            object: uiView,
            queue: .main
        ) { _ in
            previewLayer.frame = uiView.layer.bounds
        }
    }
}

方法二:条件渲染+直接传入已初始化会话

如果坚持在ContentView中条件渲染视图,空白问题通常是因为会话未启动或预览层未正确关联。

1. 完善CameraManager的会话初始化逻辑

确保会话配置完成后启动:

class CameraManager: ObservableObject {
    @Published var isSessionInitialized = false
    var captureSession = AVCaptureSession()
    
    func setupSession() {
        // 省略权限请求、输入输出配置等初始化代码
        captureSession.startRunning()
        isSessionInitialized = true
    }
}

2. 修改VideoPreviewView

将预览层创建逻辑放在makeUIView(仅执行一次):

struct VideoPreviewView: UIViewRepresentable {
    let captureSession: AVCaptureSession
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.layer.bounds
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)
        
        // 监听视图尺寸变化
        NotificationCenter.default.addObserver(
            forName: UIView.boundsDidChangeNotification,
            object: view,
            queue: .main
        ) { _ in
            previewLayer.frame = view.layer.bounds
        }
        
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // 无需额外逻辑
    }
}

3. 在ContentView中使用

struct ContentView: View {
    @EnvironmentObject var cameraManager: CameraManager
    
    var body: some View {
        if cameraManager.isSessionInitialized {
            VideoPreviewView(captureSession: cameraManager.captureSession)
                .edgesIgnoringSafeArea(.all)
        } else {
            Text("loading cam")
                .onAppear {
                    cameraManager.setupSession()
                }
        }
    }
}

关键注意事项

  • 必须确保CameraManager中isSessionInitialized = true之前,captureSession已完成配置并调用startRunning(),否则预览层会因关联未运行的会话显示空白。
  • 预览层的frame需跟随视图bounds变化,否则旋转屏幕或视图尺寸改变时会显示异常,通过监听UIView.boundsDidChangeNotification可解决此问题。

内容的提问来源于stack exchange,提问作者Jason Eveleth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 08:48:09