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

Swift AVFoundation视图更新时发布更新警告排查求助

解决AVFoundation录制代码中“Publishing changes from within view updates is not allowed”警告

警告原因

这个警告不是@StateObject本身导致的,核心问题是:在视图更新周期(比如视图初始化、刷新过程)内,你的CameraViewModel中被@Published标记的属性被修改,触发了状态变更发布,违反了SwiftUI的视图更新规则,从而引发警告。

你的代码里主要风险点集中在两处:

  1. CameraPreview的makeUIView方法中直接修改cameraModel.preview(@Published属性),而makeUIView属于视图初始化流程的一部分,此时修改会触发状态发布。
  2. 如果checkPermission或setUp方法在视图初始化阶段同步执行,其中对@Published属性的修改也会落入视图更新周期内。

解决方案

1. 避免在视图初始化流程中直接修改ViewModel的@Published属性

方案A:封装预览层创建逻辑到ViewModel

把创建预览层的逻辑放到ViewModel中,不在视图初始化流程中修改@Published属性:

class CameraViewModel: NSObject, ObservableObject, AVCaptureFileOutputRecordingDelegate {
    // ... 现有属性
    
    // 新增方法:创建预览层,不直接修改@Published属性
    func createPreviewLayer(size: CGSize) -> AVCaptureVideoPreviewLayer {
        let preview = AVCaptureVideoPreviewLayer(session: session)
        preview.frame.size = size
        preview.videoGravity = .resizeAspectFill
        return preview
    }
}

// 修改CameraPreview
struct CameraPreview: UIViewRepresentable {
    @EnvironmentObject var cameraModel : CameraViewModel
    var size: CGSize
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        
        // 直接创建预览层添加到视图,不修改ViewModel的@Published属性
        let preview = cameraModel.createPreviewLayer(size: size)
        view.layer.addSublayer(preview)
        
        DispatchQueue.global(qos: .userInitiated).async {
            cameraModel.session.startRunning()
        }
        
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) { }
}

方案B:异步修改ViewModel的preview属性

如果必须保留cameraModel.preview的@Published属性,用DispatchQueue.main.async包裹修改操作,避开视图更新周期:

func makeUIView(context: Context) -> UIView {
    let view = UIView()
    
    DispatchQueue.main.async {
        self.cameraModel.preview = AVCaptureVideoPreviewLayer(session: self.cameraModel.session)
        self.cameraModel.preview.frame.size = self.size
        self.cameraModel.preview.videoGravity = .resizeAspectFill
        view.layer.addSublayer(self.cameraModel.preview)
    }
    
    DispatchQueue.global(qos: .userInitiated).async {
        self.cameraModel.session.startRunning()
    }
    
    return view
}

2. 延迟ViewModel的权限检查与配置操作

确保checkPermission不在视图初始化的同步流程中执行,而是放到视图的onAppear回调中,并异步执行:

struct HomeStory: View {
    @StateObject var cameraModel = CameraViewModel()
    
    var body: some View {
        ZStack(alignment: .bottom) {
            CameraPreview(size: UIScreen.main.bounds.size)
                .environmentObject(cameraModel)
        }
        .onAppear {
            // 异步执行权限检查,避开视图更新周期
            DispatchQueue.main.async {
                cameraModel.checkPermission()
            }
        }
    }
}

同时修改checkPermission中的setUp调用,确保配置操作在后台队列执行,UI相关的状态变更切回主线程:

func checkPermission(){
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
        DispatchQueue.global(qos: .userInitiated).async {
            self.setUp()
            // 若有UI相关状态需要更新,切回主线程
            DispatchQueue.main.async {
                // 比如更新UI标记等操作
            }
        }
        return
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { (status) in
            if status{
                DispatchQueue.global(qos: .userInitiated).async {
                    self.setUp()
                }
            }
        }
    case .denied:
        DispatchQueue.main.async {
            self.alert.toggle()
        }
        return
    default:
        return
    }
}

总结

@StateObject是SwiftUI中管理视图生命周期内ObservableObject的正确方式,警告的根源是状态变更的时机不当,而非@StateObject的用法错误。通过异步执行状态修改、调整操作时机,就能彻底消除这个警告。

内容的提问来源于stack exchange,提问作者Ahmed Zaidan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 21:28:17