Swift: Ist there any way using @AppStorage with @Observable?
Asked Answered
I

4

21

I‘d like to store my user defaults in a class. With the old Swift data flow it was possible to use it in a class. Now, with the new system (Xcode 15, Beta 2), it doesn’t work anymore. Is there any way to use it? Maybe a workaround?

This is how it worked before:

import SwiftUI

class UserInfo: ObservableObject {

@AppStorage("username") var username: String = ""

}

And now I want to use the new system:

import SwiftUI

@Observable class UserInfo {

@AppStorage("username") var username: String = ""

}

Unfortunately, it doesn’t work.

Impoverished answered 3/7, 2023 at 17:25 Comment(1)
I think using @AppStorage inside a class is a bad idea since there is no body to be called when it changes and you aren't supposed to store models in user defaults.Albers
Y
12

Using @AppStorage ... in class UserInfo: ObservableObject works well at updating the UserDefaults. It is not observed as when you use @Published var ... variables.

Similarly to use the equivalent in the new @Observable, try using:

 @Observable class UserInfo { 

       @ObservationIgnored @AppStorage("username") var username: String = ""

 }
Yap answered 5/7, 2023 at 5:19 Comment(2)
This should be the accepted answer. Thanks for the simple and consistent solution.Hickman
This defeats the point of using Observable, as @ObservationIgnored prevents views from automatically redrawing when the @AppStorage properties change.Impala
K
9

This is the way while @AppStorage is incompatible with @Observable.

@Observable
final class SomeClass {
    var name: String {
        get {
            access(keyPath: \.name)
            return UserDefaults.standard.string(forKey: "name") ?? ""
        }
        set {
            withMutation(keyPath: \.name) {
                UserDefaults.standard.setValue(newValue, forKey: "name")
            }
        }
    }
}

Code from Swift Forums.

Klemens answered 17/9, 2023 at 19:27 Comment(1)
While the next answer is more elegant, this one retains observability.Nightcap
N
4

Other answers in this post cover, pretty much, all the real uses, but, for the sake of completeness, here’s a variant that uses the @AppStorage macro:

@Observable
class MyModel {
    
    // Externally visible properties
    
    @ObservationIgnored
    var name: String {
        get {
            access(keyPath: \.name)
            return _name
        }
        set {
            withMutation(keyPath: \.name) {
                _name = newValue
            }
        }
    }
    
    // Internal stored properties
    
    @ObservationIgnored
    @AppStorage("name")
    private var _name: String = "Bob"
    
}

Quite verbose, but doesn’t use any external macros and quite easy to extend it to support, say, storing Codable.

Nightcap answered 16/2 at 14:27 Comment(0)
I
1

In case anyone would prefer using a Swift Macro for the above code, this repo by davidsteppenbeck could help.

It's quite simple to use it:

import ObservableUserDefault

@Observable
final class StorageModel {
    @ObservableUserDefault
    @ObservationIgnored
    var name: String
}

... or ...

import ObservableUserDefault

@Observable
final class StorageModel {
    @ObservableUserDefault(.init(key: "NAME_STORAGE_KEY", defaultValue: "John Appleseed", store: .standard))
    @ObservationIgnored
    var name: String
}
Ivers answered 23/1 at 10:45 Comment(4)
Can confirm, this Swift Package works super well and is easy to use!Batson
import ObservableUserDefault is no longer available: No such module 'ObservableUserDefault' Xcode Version 16.0 beta (16A5171c)Stigma
@GalenSmith: You need to "install" the package I mentioned first: ``` The package can be installed using Swift Package Manager. To add ObservableUserDefault to your Xcode project, select File > Add Package Dependancies... and search for the repository URL: github.com/davidsteppenbeck/ObservableUserDefault.git. ```Ivers
Thanks for the @ObservableUserDefault macro. It's a good idea, but the usage steps are somewhat confusing regarding optionals and default values. The appeal to me was to speed up the way you declare a variable with a default value without typing the same variable name in multiple places. But there doesn't seem to have support for setting a default value without omitting the key parameter AND the store parameter - which adds unnecessary bloat. Anyway, hoping it will allow for that in the future.Operable

© 2022 - 2024 — McMap. All rights reserved.