iOS14中传入的Published属性无法触发onReceive,但iOS16正常
问题描述
我实现了一个接收Publisher的ViewModifier,代码如下:
struct MyViewModifier: ViewModifier { let publisher: Published<Bool>.Publisher func body(content: Content) -> some View { content .onReceive(publisher) { value in print("received") // 从未执行 } } } extension View { func listen( to publisher: Published<Bool>.Publisher ) -> some View { ModifiedContent( content: self, modifier: MyViewModifier( publisher: publisher ) ) } }
将其附加到LoadingView上,监听MyModel中@Published修饰的publisher属性,LoadingView代码:
import SwiftUI struct LoadingView: View { @ObservedObject var model: MyModel var body: some View { Text("test") .listen( to: model.$publisher ) } } }
MyModel是ObservableObject,publisher为@Published属性。通过MyViewController创建并传入model,代码:
final class MyViewController: UIHostingController<LoadingView> { let model: MyModel init() { model = LoadingModel() super.init(rootView: LoadingView(model: model)) view.backgroundColor = .clear view.translatesAutoresizingMaskIntoConstraints = false } @MainActor required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func togglePublisher(on: Bool) { model.publisher = on } }
在iOS16中修改model.publisher值时,onReceive能正常响应,但在iOS14中print("received")从未执行,请问原因是什么?
原因分析
这是iOS14中SwiftUI对@Published publisher的处理机制缺陷导致的:
Published<T>.Publisher是值类型,在iOS14中,每次访问model.$publisher都会生成一个新的Publisher实例;- 当View的body重新计算时(比如
ObservedObject的任意属性变化),会重新创建MyViewModifier实例并传入新的Publisher,之前的订阅会被销毁; - 更关键的是,iOS14中单独传递的
Published.Publisher无法正确关联到ObservableObject的生命周期,导致属性变化时,publisher无法将事件传递到onReceive的回调中。
iOS16中SwiftUI修复了这个问题,优化了@Published publisher的传递逻辑,确保即使作为值类型传递,也能维持与ObservableObject的绑定关系,正确触发订阅回调。
解决方案
可以采用以下几种方式解决iOS14的兼容性问题:
方案一:改用Binding结合onChange监听
修改ViewModifier,通过Binding<Bool>接收值,使用onChange监听变化:
struct MyViewModifier: ViewModifier { let binding: Binding<Bool> func body(content: Content) -> some View { content .onChange(of: binding.wrappedValue) { value in print("received") } } } extension View { func listen(to binding: Binding<Bool>) -> some View { ModifiedContent(content: self, modifier: MyViewModifier(binding: binding)) } }
在LoadingView中调用时传入Binding:
Text("test") .listen(to: $model.publisher)
方案二:在ViewModifier中持有ObservedObject实例
让ViewModifier直接持有MyModel实例,通过@ObservedObject维持生命周期绑定:
struct MyViewModifier: ViewModifier { @ObservedObject var model: MyModel func body(content: Content) -> some View { content .onReceive(model.$publisher) { value in print("received") } } } extension View { func listen(to model: MyModel) -> some View { ModifiedContent(content: self, modifier: MyViewModifier(model: model)) } }
在LoadingView中调用时传入model实例:
Text("test") .listen(to: model)
方案三:直接在View中使用onChange(最简洁)
如果不需要复用监听逻辑,可以直接在LoadingView中使用onChange,跳过自定义ViewModifier:
Text("test") .onChange(of: model.publisher) { value in print("received") }
内容的提问来源于stack exchange,提问作者Tometoyou
相关产品推荐
相关产品推荐

