ios 当@AppStorage变量直接绑定到SwiftUI Toggle时,为什么不为它设置willSet触发器?

wqlqzqxt  于 2023-08-08  发布在  iOS
关注(0)|答案(1)|浏览(99)

我使用@AppStorage沿着SwiftUI Toggle来管理UserDefaults变量。我的期望是在变量由于Toggle交互而发生变化时执行一些操作,因此我需要在@AppStorage变量的willSet部分执行一些代码。
但是,当我直接将@AppStorage变量绑定到Toggle的isOn状态时,willSet不会触发。相比之下,当我将@State变量绑定到Toggle并在@State变量更改时修改@AppStorage变量时,willSet中的代码确实会被执行。
为什么会这样呢?为什么Toggle对@AppStorage变量的内部修改似乎绕过了我的willSet?有没有更优雅的方式来达到我的要求?
下面是示例代码来说明这个问题:

struct ContentView: View {
    @State var stateVariable = false
    @AppStorage("userDefaultVariable") var userDefaultVariable: Bool = false {
        willSet {
            print("userDefaultVariable will set, newValue: \(newValue)")
        }
    }
    var body: some View {
        VStack{
            Toggle("Directly modify userDefaultVariable",isOn: $userDefaultVariable)    
            Toggle("Indirectly modify userDefaultVariable",isOn: $stateVariable) 
                .onChange(of: stateVariable, perform: { value in
                    userDefaultVariable = value
                })
        }
    }
}

字符串
在此代码中,“直接修改userDefaultVariable”切换不会触发userDefaultVariable的willSet中的print语句。但是,“间接修改userDefaultVariable”切换在修改@State变量stateVariable时会触发print语句,而stateVariable又会修改userDefaultVariable。

emeijp43

emeijp431#

AppStorageState这样的属性 Package 器被编译成 Package 器类型的存储属性,以及获取和设置存储属性的wrappedValue的计算属性。还有一个以$为前缀的computed属性,它获取projectedValue

@AppStorage("userDefaultVariable") var userDefaultVariable: Bool = false

字符串
变成:

var _userDefaultVariable = AppStorage(wrappedValue: false, "userDefaultVariable")

var userDefaultVariable: Bool {
    get { _userDefaultVariable.wrappedValue }
    set { _userDefaultVariable.wrappedValue = newValue }
}

var $userDefaultVariable: Binding<Bool> {
    _userDefaultVariable.projectedValue
}


根据this post here,当您添加willSet时,它将应用于计算属性userDefaultVariable,而不是_userDefaultVariable$userDefaultVariable。因此,只要您没有直接使用userDefaultVariable = ...设置它,就不会触发willSet
更复杂的是,Binding.wrappedValueAppStorage.wrappedValue都有一个nonmutating setter。
我认为这里唯一的解决方案是制作自己的Binding,并在set:参数的开头添加任何您想要的代码:

Toggle("Directly modify userDefaultVariable",isOn: Binding(get: { userDefaultVariable }, set: { newValue in
    // do your willSet things here...
    userDefaultVariable = newValue
}))


您可以将其重构到自己的属性 Package 器中:

@propertyWrapper
struct Observed<Wrapped> {
    var wrappedValue: AppStorage<Wrapped>
    
    var willSet: (Wrapped) -> Void
    
    var projectedValue: Binding<Wrapped> {
        Binding {
            wrappedValue.wrappedValue
        } set: { newValue in
            willSet(newValue)
            wrappedValue.wrappedValue = newValue
        }
    }
}


使用方法:

struct ContentView: View {
    @Observed(willSet: { newValue in
        print("WillSet: \(newValue)") 
        // do other things...
    })
    @AppStorage("userDefaultVariable") var userDefaultVariable: Bool = false
    var body: some View {
        VStack{
            Toggle("Directly modify userDefaultVariable",isOn: $userDefaultVariable)
        }
    }
}

相关问题