UserDefaults Binding with Toggle in SwiftUI
Update
------- iOS 14: -------
Starting iOS 14, there is now a very very simple way to read and write to UserDefaults.
Using a new property wrapper called @AppStorage
Here is how it could be used:
import SwiftUIstruct ContentView : View { @AppStorage("settingActivated") var settingActivated = false var body: some View { NavigationView { Form { Toggle(isOn: $settingActivated) { Text("Setting Activated") } }.navigationBarTitle(Text("Settings")) } }}
That's it! It is so easy and really straight forward. All your information is being saved and read from UserDefaults.
-------- iOS 13: ---------
A lot has changed in Swift 5.1. BindableObject
has been completely deprecated. Also, there has been significant changes in PassthroughSubject
.
For anyone wondering to get this to work, below is the working example for the same. I have reused the code of 'gohnjanotis' to make it simple.
import SwiftUIimport Combinestruct ContentView : View { @ObservedObject var settingsStore: SettingsStore var body: some View { NavigationView { Form { Toggle(isOn: $settingsStore.settingActivated) { Text("Setting Activated") } }.navigationBarTitle(Text("Settings")) } }}class SettingsStore: ObservableObject { let willChange = PassthroughSubject<Void, Never>() var settingActivated: Bool = UserDefaults.settingActivated { willSet { UserDefaults.settingActivated = newValue willChange.send() } }}extension UserDefaults { private struct Keys { static let settingActivated = "SettingActivated" } static var settingActivated: Bool { get { return UserDefaults.standard.bool(forKey: Keys.settingActivated) } set { UserDefaults.standard.set(newValue, forKey: Keys.settingActivated) } }}
With help both from this video by azamsharp and this tutorial by Paul Hudson, I've been able to produce a toggle that binds to UserDefaults and shows whichever change you've assigned to it instantaneously.
- Scene Delegate:
Add this line of code under 'window' variable
var settingsStore = SettingsStore()
And modify window.rootViewController to show this
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(settingsStore))
- SettingsStore:
import Foundationclass SettingsStore: ObservableObject { @Published var isOn: Bool = UserDefaults.standard.bool(forKey: "isOn") { didSet { UserDefaults.standard.set(self.isOn, forKey: "isOn") } }}
- SettingsStoreMenu
If so you wish, create a SwiftUI View called this and paste:
import SwiftUIstruct SettingsStoreMenu: View { @ObservedObject var settingsStore: SettingsStore var body: some View { Toggle(isOn: self.$settingsStore.isOn) { Text("") } }}
- Last but not least
Don't forget to inject SettingsStore to SettingsStoreMenu from whichever Main View you have, such as
import SwiftUIstruct MainView: View { @EnvironmentObject var settingsStore: SettingsStore @State var showingSettingsStoreMenu: Bool = false var body: some View { HStack { Button("Go to Settings Store Menu") { self.showingSettingsStoreMenu.toggle() } .sheet(isPresented: self.$showingSettingsStoreMenu) { SettingsStoreMenu(settingsStore: self.settingsStore) } } }}
(Or whichever other way you desire.)
This seam to work well :
enum BackupLocalisations: String, CaseIterable, Hashable, Identifiable { case iPhone = "iPhone" case iCloud = "iCloud" var name: String { return self.rawValue } var id: BackupLocalisations {self}}enum Keys { static let iCloudIsOn = "iCloudIsOn" static let backupLocalisation = "backupLocalisation" static let backupsNumber = "backupsNumber"}
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? var settings = Settings()…/… let contentView = ContentView() .environmentObject(settings)… }
class Settings: ObservableObject { @Published var iCloudIsOn: Bool = UserDefaults.standard.bool(forKey: Keys.iCloudIsOn) { didSet { UserDefaults.standard.set(self.iCloudIsOn, forKey: Keys.iCloudIsOn) } } @Published var backupLocalisation: String = UserDefaults.standard.object(forKey: Keys.backupLocalisation) as? String ?? "iPhone" { didSet { UserDefaults.standard.set(self.backupLocalisation, forKey: Keys.backupLocalisation) } } @Published var backupsNumber: Int = UserDefaults.standard.integer(forKey: Keys.backupsNumber) { didSet { UserDefaults.standard.set(self.backupsNumber, forKey: Keys.backupsNumber) } }}
struct ContentView: View { @ObservedObject var settings: Settings var body: some View { NavigationView { Form { Section(footer: Text("iCloud is \(UserDefaults.standard.bool(forKey: Keys.iCloudIsOn) ? "on" : "off")")) { Toggle(isOn: self.$settings.iCloudIsOn) { Text("Use iCloud") } } Section { Picker(selection: $settings.backupLocalisation, label: Text("\(self.settings.backupsNumber) sauvegarde\(self.settings.backupsNumber > 1 ? "s" : "") sur").foregroundColor(Color(.label))) { ForEach(BackupLocalisations.allCases) { b in Text(b.name).tag(b.rawValue) } } Stepper(value: self.$settings.backupsNumber) { Text("Nombre de sauvegardes") } } }.navigationBarTitle(Text("Settings")) } }}
struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView().environmentObject(Settings()) }}
Xcode 11.3.1