How do I use UserDefaults with SwiftUI?
The approach from caram is in general ok but there are so many problems with the code that SmushyTaco did not get it work. Below you will find an "Out of the Box" working solution.
1. UserDefaults propertyWrapper
import Foundationimport Combine@propertyWrapperstruct UserDefault<T> { let key: String let defaultValue: T init(_ key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } }}
2. UserSettings class
final class UserSettings: ObservableObject { let objectWillChange = PassthroughSubject<Void, Never>() @UserDefault("ShowOnStart", defaultValue: true) var showOnStart: Bool { willSet { objectWillChange.send() } }}
3. SwiftUI view
struct ContentView: View {@ObservedObject var settings = UserSettings()var body: some View { VStack { Toggle(isOn: $settings.showOnStart) { Text("Show welcome text") } if settings.showOnStart{ Text("Welcome") } }}
Starting from Xcode 12.0 (iOS 14.0) you can use @AppStorage
property wrapper for such types: Bool, Int, Double, String, URL
and Data
.Here is example of usage for storing String value:
struct ContentView: View { static let userNameKey = "user_name" @AppStorage(Self.userNameKey) var userName: String = "Unnamed" var body: some View { VStack { Text(userName) Button("Change automatically ") { userName = "Ivor" } Button("Change manually") { UserDefaults.standard.setValue("John", forKey: Self.userNameKey) } } }}
Here you are declaring userName
property with default value which isn't going to the UserDefaults
itself. When you first mutate it, application will write that value into the UserDefaults
and automatically update the view with the new value.
Also there is possibility to set custom UserDefaults
provider if needed via store
parameter like this:
@AppStorage(Self.userNameKey, store: UserDefaults.shared) var userName: String = "Mike"
and
extension UserDefaults { static var shared: UserDefaults { let combined = UserDefaults.standard combined.addSuite(named: "group.myapp.app") return combined }}
Notice: ff that value will change outside of the Application (let's say manually opening the plist file and changing value), View will not receive that update.
P.S. Also there is new Extension on View
which adds func defaultAppStorage(_ store: UserDefaults) -> some View
which allows to change the storage used for the View. This can be helpful if there are a lot of @AppStorage
properties and setting custom storage to each of them is cumbersome to do.
The code below adapts Mohammad Azam's excellent solution in this video:
import SwiftUIstruct ContentView: View { @ObservedObject var userDefaultsManager = UserDefaultsManager() var body: some View { VStack { Toggle(isOn: self.$userDefaultsManager.firstToggle) { Text("First Toggle") } Toggle(isOn: self.$userDefaultsManager.secondToggle) { Text("Second Toggle") } } }}class UserDefaultsManager: ObservableObject { @Published var firstToggle: Bool = UserDefaults.standard.bool(forKey: "firstToggle") { didSet { UserDefaults.standard.set(self.firstToggle, forKey: "firstToggle") } } @Published var secondToggle: Bool = UserDefaults.standard.bool(forKey: "secondToggle") { didSet { UserDefaults.standard.set(self.secondToggle, forKey: "secondToggle") } }}