Storing User Settings in a SwiftUI app using UserDefaults
Created Nov 27 2020
SWIFT1// Storing User Settings vs Storing User Data23/*4User settings can be stored in UserDefault.5UserDefault is one of the ways to store small amount of user data (max 512KB)67It's very easy to use, the data is loaded immediately at launch.89I use this in my weather app to store which unit (temperature, speed) the user has selected.1011Here's an excerpt:12*/1314enum Temperature: Int{15case fahrenheit = 016case celsius = 117case kelvin = 218}1920enum Speed: Int{21case mph = 022case kmh = 123case mps = 224}2526/*27Tip I learned: I store things I need to propagate throughout the app in an "observable" class28That way, every time one of the published field (the ones with the @Published property wrapper)29changes, any view watching this "store" class will be updated.3031And yes, this sounds a lot like what frontend developers are familiar with when using things32like Redux :)33*/34class Store: ObservableObject {35@Published var temperature: Temperature {36/*37This is what we call a "property observer". Here, it lets us execute code whenever38the temperature property changes. In this case, we're simply telling Swift, to save39the temperature property in UserDefault with the key "temperature" whenever temperature40changes41*/42didSet {43UserDefaults.standard.set(temperature.rawValue, forKey: "temperature")44}45}46@Published var speed: Speed {47didSet {48UserDefaults.standard.set(speed.rawValue, forKey: "speed")49}50}515253init() {54/*55Here we load the data from user default, so that the proper temperature and speed units56are available the moment the app loads and this class is instantiated.57*/58self.temperature = (UserDefaults.standard.object(forKey: "temperature") == nil ? Temperature.fahrenheit : Temperature(rawValue: UserDefaults.standard.object(forKey: "temperature") as! Int)) ?? Temperature.fahrenheit59self.speed = (UserDefaults.standard.object(forKey: "speed") == nil ? Speed.mph : Speed(rawValue: UserDefaults.standard.object(forKey: "speed") as! Int)) ?? Speed.mph60}61}6263struct SettingsView: View {64// To get an object present in the environment, we use the @EnvironmentObject property wrapper65@EnvironmentObject var store: Store6667var body: some View {68VStack {69VStack {70HStack {71Text("Temperature")72.font(.system(size: 18, weight: .medium))73.foregroundColor(.secondary)74Spacer()75}76Picker(selection: $store.temperature, label: Text("Temperature"), content: {77Text("Fahrenheit °F 🇺🇸").tag(Temperature.fahrenheit)78Text("Celsius °C 🌍").tag(Temperature.celsius)79Text("Kelvin °K 🤓").tag(Temperature.kelvin)80})81.pickerStyle(SegmentedPickerStyle())82}83.padding(.top, 20)84VStack {85HStack {86Text("Wind Speed")87.font(.system(size: 18, weight: .medium))88.foregroundColor(.secondary)89Spacer()90}91Picker(selection: $store.speed, label: Text("Speed"), content: {92Text("mph 🇺🇸").tag(Speed.mph)93Text("km/h 🌍").tag(Speed.kmh)94Text("m/s 🤓").tag(Speed.mps)95})96.pickerStyle(SegmentedPickerStyle())97}98.padding(.top, 20)99}100.padding([.leading, .trailing], 12)101}102}103104struct ContentView: some View {105@StateObject var store = Store()106107var body: some View {108VStack {109SettingsView()110}111/*112113In SwiftUI, using the environmentObject modifier will let you "inject" an object into the environment.114Here, every child view will have a copy of the parent's store object and will also be115subscribe to any updates of that object (since it's an observableObject with published fields.116117And yes, frontend developers friends, this is very similar to what we're used to doing when using118Contexts in React!119*/120.environmentObject(store)121}122}