SwiftUI闭包捕获值不更新现象
SwiftUI闭包捕获值不更新现象

SwiftUI闭包捕获值不更新现象

什么是“闭包捕获值不更新”?

在 SwiftUI 的 .sheet、.alert、.onAppear、.animation 等等中使用闭包(也叫 trailing closure),Swift 会在“写这个闭包时”捕获当时的值,而不是“点下去的时候”的值。

简单示例:

struct ContentView: View {
    @State private var index = 0
    @State private var showSheet = false
    var body: some View {
        VStack {
            Button("点我") {
                index = 3
                showSheet = true
            }
            .sheet(isPresented: $showSheet) {
                Text("当前 index 是 \(index)")
            }
        }
        .padding()
    }
}

在这个代码中,可以看到当点击按钮时,sheet显示的index是0,而不是设置的3。

这就是闭包捕获值不更新现象,index值是最开始的值,而不是更新后的值。

为什么会发生?

SwiftUI会预先创建.sheet的内容,并不是每次点击都会重新创建。

也就是说,.sheet的闭包会提前以值类型保存下来,里面的数值是当时的快照。

这里的关键点是,.sheet是一个“逃逸闭包”,SwiftUI会在某些时候只捕获它第一次展示时的上下文值,这就导致看到的index不是点击时的最新值,而是旧值。

这里的“值不更新”的问题,也就是闭包捕获值不变。

正确做法

当需要在.sheet,.alert等UI控件中动态传值时,应该使用对应的方法传值:

import SwiftUI
extension Int: Identifiable {
    public var id: Int { self }
}
struct ContentView: View {
    @State private var index: Int? = nil
    var body: some View {
        VStack {
            Button("点我") {
                index = 3
            }
            .sheet(item: $index) { value in
                Text("当前 index 是 \(value)")
            }
        }
        .padding()
    }
}

在这里使用的是sheet(item:)方法将index值传递进来。当使用sheet(item:)时,监听的不再是布尔值,而是监听item的变化本身。

当item从nil变成某只值(比如3)时,SwiftUI会重新创建Sheet View,并将item的当前值作为参数传入,所以闭包中的value就是最新的值。

这里还新增了一个Int扩展,用于遵循Identifiable协议。

如果不添加扩展,sheet(item:)绑定的过程中会提示报错:

Instance method 'sheet(item:onDismiss:content:)' requires that 'Int' conform to 'Identifiable'

报错原因为,SwiftUI要求item必须遵循Identifiable协议的类型,而Int并不是Identifiable。

相关文章

1、Swift深入理解闭包捕获机制:https://fangjunyu.com/2024/11/28/swift%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3%e9%97%ad%e5%8c%85%e6%8d%95%e8%8e%b7%e6%9c%ba%e5%88%b6/

2、Swift深入理解闭包表达式:https://fangjunyu.com/2024/11/26/swift%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3%e9%97%ad%e5%8c%85%e8%a1%a8%e8%be%be%e5%bc%8f/

3、Swift闭包的三种形式以及理解闭包的嵌套函数:https://fangjunyu.com/2024/10/23/swift%e9%97%ad%e5%8c%85%e7%9a%84%e4%b8%89%e7%a7%8d%e5%bd%a2%e5%bc%8f%e4%bb%a5%e5%8f%8a%e7%90%86%e8%a7%a3%e9%97%ad%e5%8c%85%e7%9a%84%e5%b5%8c%e5%a5%97%e5%87%bd%e6%95%b0/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注