Storing User Settings in a SwiftUI app using UserDefaults

Created Nov 27 2020

SWIFT
1
// Storing User Settings vs Storing User Data
2
3
/*
4
User settings can be stored in UserDefault.
5
UserDefault is one of the ways to store small amount of user data (max 512KB)
6
7
It's very easy to use, the data is loaded immediately at launch.
8
9
I use this in my weather app to store which unit (temperature, speed) the user has selected.
10
11
Here's an excerpt:
12
*/
13
14
enum Temperature: Int{
15
case fahrenheit = 0
16
case celsius = 1
17
case kelvin = 2
18
}
19
20
enum Speed: Int{
21
case mph = 0
22
case kmh = 1
23
case mps = 2
24
}
25
26
/*
27
Tip I learned: I store things I need to propagate throughout the app in an "observable" class
28
That way, every time one of the published field (the ones with the @Published property wrapper)
29
changes, any view watching this "store" class will be updated.
30
31
And yes, this sounds a lot like what frontend developers are familiar with when using things
32
like Redux :)
33
*/
34
class Store: ObservableObject {
35
@Published var temperature: Temperature {
36
/*
37
This is what we call a "property observer". Here, it lets us execute code whenever
38
the temperature property changes. In this case, we're simply telling Swift, to save
39
the temperature property in UserDefault with the key "temperature" whenever temperature
40
changes
41
*/
42
didSet {
43
UserDefaults.standard.set(temperature.rawValue, forKey: "temperature")
44
}
45
}
46
@Published var speed: Speed {
47
didSet {
48
UserDefaults.standard.set(speed.rawValue, forKey: "speed")
49
}
50
}
51
52
53
init() {
54
/*
55
Here we load the data from user default, so that the proper temperature and speed units
56
are available the moment the app loads and this class is instantiated.
57
*/
58
self.temperature = (UserDefaults.standard.object(forKey: "temperature") == nil ? Temperature.fahrenheit : Temperature(rawValue: UserDefaults.standard.object(forKey: "temperature") as! Int)) ?? Temperature.fahrenheit
59
self.speed = (UserDefaults.standard.object(forKey: "speed") == nil ? Speed.mph : Speed(rawValue: UserDefaults.standard.object(forKey: "speed") as! Int)) ?? Speed.mph
60
}
61
}
62
63
struct SettingsView: View {
64
// To get an object present in the environment, we use the @EnvironmentObject property wrapper
65
@EnvironmentObject var store: Store
66
67
var body: some View {
68
VStack {
69
VStack {
70
HStack {
71
Text("Temperature")
72
.font(.system(size: 18, weight: .medium))
73
.foregroundColor(.secondary)
74
Spacer()
75
}
76
Picker(selection: $store.temperature, label: Text("Temperature"), content: {
77
Text("Fahrenheit °F 🇺🇸").tag(Temperature.fahrenheit)
78
Text("Celsius °C 🌍").tag(Temperature.celsius)
79
Text("Kelvin °K 🤓").tag(Temperature.kelvin)
80
})
81
.pickerStyle(SegmentedPickerStyle())
82
}
83
.padding(.top, 20)
84
VStack {
85
HStack {
86
Text("Wind Speed")
87
.font(.system(size: 18, weight: .medium))
88
.foregroundColor(.secondary)
89
Spacer()
90
}
91
Picker(selection: $store.speed, label: Text("Speed"), content: {
92
Text("mph 🇺🇸").tag(Speed.mph)
93
Text("km/h 🌍").tag(Speed.kmh)
94
Text("m/s 🤓").tag(Speed.mps)
95
})
96
.pickerStyle(SegmentedPickerStyle())
97
}
98
.padding(.top, 20)
99
}
100
.padding([.leading, .trailing], 12)
101
}
102
}
103
104
struct ContentView: some View {
105
@StateObject var store = Store()
106
107
var body: some View {
108
VStack {
109
SettingsView()
110
}
111
/*
112
113
In SwiftUI, using the environmentObject modifier will let you "inject" an object into the environment.
114
Here, every child view will have a copy of the parent's store object and will also be
115
subscribe to any updates of that object (since it's an observableObject with published fields.
116
117
And yes, frontend developers friends, this is very similar to what we're used to doing when using
118
Contexts in React!
119
*/
120
.environmentObject(store)
121
}
122
}