alltom.com,
Archive,
Currently Reading,
Have Read,
Micro.blog
Subscribe with RSS or @tom@micro.alltom.com
I had a @State
variable in a SwiftUI view that I wanted to persist using iCloud’s key/value storage. Seemed like it should have been easy… and it was!
The gist is: store the value in a Binding
with custom get
and set
. In my case, the value is an enum implementing Codable using strategies Dmitrii Ivanov summarizes here, so I just replaced this:
@State private var myvar: MyEnum
with this:
private var myvar: Binding<MyEnum> {
Binding<MyEnum>(get: {
if let data = NSUbiquitousKeyValueStore().data(forKey: "myvar") {
if let state = try? PropertyListDecoder().decode(MyEnum.self, from: data) {
return state
}
}
return MyEnum.someDefaultSetting
}, set: {
if let encoded = try? PropertyListEncoder().encode($0) {
let store = NSUbiquitousKeyValueStore()
store.set(encoded, forKey: "myvar")
store.synchronize()
}
})
}
It’s begging to be encapsulated for reuse, but I’ve only ever needed it once so far. :)
Unfortunately, you have to change all the code that accesses the value of myvar
and gets bindings for myvar
. Now self.myvar
refers to the binding and you must use self.myvar.wrappedValue
to refer to the current value. But the substitution is simple:
Access value | Access binding | |
---|---|---|
@State | self.myvar | self.$myvar |
Binding | self.myvar.wrappedValue | self.myvar |
Creating a binding this way is useful in general to perform custom actions when a @State
’s value changes, which you can’t do by defining didSet
on a @State
variable. If you try using didSet
, it will be called when you set the value directly, like self.myvar = foo
, but it will not be called if the value is set via the self.$myvar
binding, such as when you bind it to an input widget.
Addendum: I’ve found that a Binding isn’t a drop-in replacement for @State
. A view will redraw in response to a change in @State
, but will not do so for a custom Binding
. So while the code above is useful, you still need a @State
variable for a reactive UI. The easiest way I’ve found to do that is to always use the custom Binding
for mutations, such as by editable controls (not the @State
’s generated binding, $binding
), and to set the @State
in the custom Binding
’s set
method. You must also subscribe to iCloud change events and use those to update the value via the custom Binding
.