SwiftUI中@Published修饰的UIImage属性未触发视图更新,MJPEG流无法显示
问题原因
- 核心错误:
CameraView中使用@ObservedObject修饰并在视图内部初始化CameraViewModel,不符合属性包装器的使用规范。@ObservedObject不负责管理实例生命周期,SwiftUI 视图重绘时会反复重建CameraViewModel实例,导致新接收的图片赋值给了已销毁的旧实例,视图绑定的新实例始终保留默认值。 - 逻辑错误:
onAppear修饰符错误挂载在Button组件上,仅当 Button 渲染时才会触发流加载方法,逻辑不符合预期。 - 潜在问题:当前 MJPEG 流解析逻辑仅在收到响应头时拼接生成图片,不符合 MJPEG 按 boundary 分隔帧的标准规范,大概率会出现丢帧、图片解析失败的问题。
解决方案
1. 修正 ViewModel 属性包装器
将视图内部初始化的 @ObservedObject 替换为 @StateObject,@StateObject 是 SwiftUI 专门用于在视图内部初始化并持有 ObservableObject 生命周期的属性包装器,不会随视图重绘被销毁:
// 替换前 @ObservedObject var cameraViewModel: CameraViewModel = CameraViewModel() // 替换后 @StateObject var cameraViewModel: CameraViewModel = CameraViewModel()
如果
CameraViewModel是从上层视图传入的,则不需要在当前视图初始化,直接声明为@ObservedObject var cameraViewModel: CameraViewModel即可,由上层视图负责实例的生命周期管理。
2. 调整 onAppear 挂载位置
将 onAppear 修饰符挂载到外层容器视图上,保证页面加载时就触发流加载逻辑:
struct CameraView: View { @Binding var showCamera: Bool @StateObject var cameraViewModel: CameraViewModel = CameraViewModel() var body: some View { VStack { Image(uiImage: cameraViewModel.cameraImage!) .aspectRatio(contentMode: .fit) .frame(height: 200) Button("Close") { cameraViewModel.stop() showCamera.toggle() } } .onAppear { // 移到VStack层级上 cameraViewModel.play() } } }
3. 可选优化项
- MJPEG 解析逻辑优化:先从响应头的
Content-Type字段获取 boundary 标识,按 boundary 拆分收到的数据流,拿到完整的单帧数据后再转换为 UIImage,避免无效解析。 - 空值容错处理:去掉
cameraImage的强制解包,避免空值崩溃:
// CameraViewModel 中定义 @Published var cameraImage: UIImage? = UIImage(systemName: "cloud.heavyrain.fill") // CameraView 中渲染 if let image = cameraViewModel.cameraImage { Image(uiImage: image) .aspectRatio(contentMode: .fit) .frame(height: 200) } else { ProgressView() }
内容的提问来源于stack exchange,提问作者esbenr
相关产品推荐
相关产品推荐

