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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 16:13:16