SwiftUI中refreshable闭包仅捕获一次self的问题及解决咨询
SwiftUI
refreshable 闭包捕获问题的解决办法 经测试发现,SwiftUI的refreshable闭包存在一个问题:它会在View首次初始化时固定捕获self的副本,即便View的body因状态更新重新调用,这个闭包也不会重新创建。比如你提供的示例中,切换ParentView的Toggle后,ChildView显示的Model ID已经更新,但下拉刷新时打印的仍是最初的ID。
以下是几种可行的解决办法:
办法一:通过id修饰器强制视图重建
给ChildView添加.id()修饰器,将Model的唯一标识作为ID值。SwiftUI会在ID变化时销毁旧视图并创建新视图,这样refreshable闭包会重新捕获新的self实例。
修改ParentView中ChildView的调用代码:
ChildView(m: self.one ? self.m1 : self.m2) .id(self.one ? self.m1.id : self.m2.id)
办法二:用动态变量中转需要捕获的值
将需要在refreshable中使用的属性存入@State变量,并通过onChange监听Model的变化来更新这个变量,确保闭包能捕获到最新值。
修改ChildView的代码:
struct ChildView: View { let m: Model @State private var currentId: Int init(m: Model) { self.m = m self._currentId = State(initialValue: m.id) } var body: some View { List { Text("Model \(self.m.id)") } .onChange(of: m) { newModel in currentId = newModel.id } .refreshable { print("\(currentId)") } } }
办法三:实现自定义的动态下拉刷新修饰器
如果不想依赖视图重建,可以自己封装基于UIRefreshControl的刷新逻辑,确保每次触发刷新时都能获取到当前的视图状态。
自定义修饰器代码:
struct DynamicRefreshModifier: ViewModifier { let action: @escaping () -> Void func body(content: Content) -> some View { content .background( RefreshControlView(action: action) ) } private struct RefreshControlView: UIViewRepresentable { let action: @escaping () -> Void func makeUIView(context: Context) -> UIView { let view = UIView() view.isHidden = true return view } func updateUIView(_ uiView: UIView, context: Context) { // 获取父级滚动视图 guard let scrollView = uiView.superview?.superview as? UIScrollView else { return } // 移除旧的刷新控件 scrollView.refreshControl?.removeTarget(context.coordinator, action: nil, for: .allEvents) // 创建并绑定新的刷新控件 let refreshControl = UIRefreshControl() refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.handleRefresh), for: .valueChanged) scrollView.refreshControl = refreshControl } func makeCoordinator() -> Coordinator { Coordinator(action: action) } class Coordinator: NSObject { let action: @escaping () -> Void init(action: @escaping () -> Void) { self.action = action } @objc func handleRefresh(_ sender: UIRefreshControl) { action() sender.endRefreshing() } } } } extension View { func dynamicRefreshable(action: @escaping () -> Void) -> some View { self.modifier(DynamicRefreshModifier(action: action)) } }
使用方式:
在ChildView中替换原有的refreshable:
struct ChildView: View { let m: Model var body: some View { List { Text("Model \(self.m.id)") } .dynamicRefreshable { print("\(self.m.id)") } } }
内容的提问来源于stack exchange,提问作者bteapot
相关产品推荐
相关产品推荐

