如何让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
相关产品推荐
相关产品推荐

