SwiftUI中如何等待StateObject类完成初始化?
解决SwiftUI中StateObject异步初始化的问题
首先明确:SwiftUI没有直接等待StateObject初始化的专属方法,但你可以通过拆分异步逻辑+状态驱动视图的方式解决这个问题,核心思路是把异步操作从初始化器里移出来,用@Published属性跟踪状态,让视图根据状态动态更新。
第一步:重构VideoStream类
因为NSObject的初始化器不能是异步的,所以把权限请求、Session配置这些异步逻辑从init里抽出来,放到单独的异步方法中,同时用@Published属性跟踪Session和当前状态:
import AVFoundation import Combine class VideoStream: NSObject, ObservableObject { // 用可选类型+@Published跟踪Session状态 @Published var session: AVCaptureSession? @Published var luminosityReading: Double = 0.0 // 定义状态枚举,跟踪初始化流程 enum Status { case idle case requestingPermission case ready case denied } @Published var status: Status = .idle override init() { super.init() // 这里只做基础初始化,不处理异步逻辑 } // 单独的异步方法处理权限和Session配置 func setupSession() async { status = .requestingPermission // 1. 检查/请求相机权限 let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .authorized: await configureSession() case .notDetermined: let granted = await AVCaptureDevice.requestAccess(for: .video) if granted { await configureSession() } else { self.status = .denied } case .denied, .restricted: self.status = .denied @unknown default: self.status = .denied } } // 配置AVCaptureSession的异步方法 private func configureSession() async { let newSession = AVCaptureSession() newSession.sessionPreset = .high // 这里写你的Session配置逻辑(添加输入输出、设置元数据代理等) // 示例:添加视频输入 guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back), let input = try? AVCaptureDeviceInput(device: device) else { status = .denied return } if newSession.canAddInput(input) { newSession.addInput(input) } // 配置亮度元数据输出(示例) let metadataOutput = AVCaptureMetadataOutput() if newSession.canAddOutput(metadataOutput) { newSession.addOutput(metadataOutput) // 设置元数据代理,处理亮度数据 metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue(label: "LuminosityQueue")) metadataOutput.metadataObjectTypes = [.brightness] } newSession.startRunning() session = newSession status = .ready } } // 扩展实现AVCaptureMetadataOutputObjectsDelegate,处理亮度数据 extension VideoStream: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let brightnessObject = metadataObjects.first as? AVMetadataBrightnessObject else { return } DispatchQueue.main.async { self.luminosityReading = brightnessObject.brightness } } }
第二步:修改ContentView,根据状态动态渲染
在视图的onAppear触发异步初始化方法,然后根据videoStream.status显示不同内容:
struct ContentView: View { @StateObject var videoStream = VideoStream() var body: some View { VStack(spacing: 20) { // 根据状态显示不同内容 switch videoStream.status { case .idle, .requestingPermission: ProgressView("正在准备相机...") .frame(width: 300, height: 300) case .ready: if let session = videoStream.session { VideoPreviewHolder(runningSession: session) .frame(width: 300, height: 300) } case .denied: Text("相机权限已拒绝,请在设置中开启") .foregroundColor(.red) .frame(width: 300, height: 300) } Text(String(format: "%.2f Lux", videoStream.luminosityReading)) .font(.largeTitle) } .onAppear { // 视图出现时触发异步初始化 Task { await videoStream.setupSession() } } } } // 保留你的VideoPreviewHolder实现 struct VideoPreviewHolder: UIViewRepresentable { let runningSession: AVCaptureSession func makeUIView(context: Context) -> UIView { let view = UIView(frame: .zero) let captureLayer = AVCaptureVideoPreviewLayer(session: runningSession) captureLayer.frame = view.bounds captureLayer.videoGravity = .resizeAspectFill view.layer.addSublayer(captureLayer) return view } func updateUIView(_ uiView: UIView, context: Context) { // 可以根据需要更新视图 } }
关键说明
- 为什么不能用异步初始化器?因为
NSObject是Objective-C兼容类,而Objective-C没有异步初始化的概念,所以Swift不允许NSObject子类拥有async init,这就是你遇到报错的原因。 - 状态驱动:通过
@Published的status属性,让SwiftUI自动感知状态变化,动态切换视图内容,避免在Session未初始化时传递空值给VideoPreviewHolder。 - 异步逻辑隔离:把权限请求、Session配置放到单独的异步方法中,用
Task在onAppear里触发,既符合SwiftUI的生命周期,又能处理异步操作。
内容的提问来源于stack exchange,提问作者John Harrington
相关产品推荐
相关产品推荐

