read

@AppStorage is a property wrapper for UserDefaults, provided by SwiftUI framework. It is very simple to use, but in practice, some simple things are not obvious.

I had a hiccup when I try to “observe” an AppStorage using Combine. Let’s use this as an example:

@AppStorage("multiCamEnabled") var multiCamEnabled = true

If I try to observe with $multiCamEnabled.sink(...), that is not possible because $multiCamEnabled is a Binding, not a Publisher (if it is a @Published, then will be fine).

How to observe?

One solution is to use SwiftUI onChange modifier.

Text("Any view")
.onChange(of: multiCamEnabled) {
  ...
}

But onChange is limited to SwiftUI. What if you want to observe outside of the view eg. in a view model?

How to observe outside a View?

There is no way to observe an AppStorage/Binding directly. If you know, tell me. 🙏🏻

A workaround is to fallback on UserDefaults, which we know very well how to observe one.

UserDefaults.standard.publisher(for: \.multiCamEnabled)
  .sink { ... }

Note that UserDefaults need to be setup like this in order to use publisher(for:):

extension UserDefaults {
    @objc var multiCamEnabled: Bool {
        get { bool(forKey: .multiCamEnabled) }
        set { set(newValue, forKey: .multiCamEnabled) }
    }
}

/// Avoid repeating the String
extension String {
    static let multiCamEnabled = "multiCamEnabled"
}

AppStorage uses UserDefaults

That is possible because AppStorage is simply using UserDefaults.

By default, it is using standard. But that can be changed with the store.

@AppStorage(.multiCamEnabled, store: UserDefaults.init(suiteName: "Shared")) var multiCamEnabled = true

Conclusion

SwiftUI is indeed re-learning everything.

AppStorage is new, yet I’m not entirely sure it is better.


Image

@samwize

¯\_(ツ)_/¯

Back to Home