Persist data with UserDefaults in SwiftUI
Small pieces of App data can be stored using UserDefaults in SwiftUI. This is particularly good for application settings rather than user data that may be better stores in a larger database or using core data. This article describes how to use UserDefaults to store application data.
Create Polygon app
Create a new iOS app to display a regular polygon shape. This displays the shape with options to change the number of sides, change the color and set whether the shape is solid or outlined. This is implemented using the MVVM pattern as described in MVVM in SwiftUI. When this app is closed and removed from memory and relaunched, the shape is reset to the original default.
Model
1struct PolygonModel {
2 var sideNumber: Int
3 var solid: Bool
4 var colorIndex: Int
5
6 fileprivate var colors:[Color] = [.red, .orange, .yellow, .green, .blue, .purple]
7
8 init(sides: Int, solid: Bool, colorIndex: Int) {
9 self.sideNumber = sides
10 self.solid = solid
11 self.colorIndex = colorIndex
12 }
13
14 public var ShapeColor: Color {
15 colors[colorIndex % colors.count]
16 }
17
18 mutating func nextColor() {
19 self.colorIndex += 1
20 }
21}
ViewModel
1class PolygonViewModel: ObservableObject {
2 @Published private var polygon = PolygonModel(
3 sides: 3,
4 solid: true,
5 colorIndex: 0)
6
7 var sides: Double {
8 get { Double(polygon.sideNumber) }
9 set { polygon.sideNumber = Int(newValue) }
10 }
11
12 var solid: Bool {
13 get { polygon.solid }
14 set { polygon.solid = newValue }
15 }
16
17 var shapeColor: Color {
18 get { return polygon.ShapeColor }
19 }
20
21 func nextColor() {
22 polygon.nextColor()
23 }
24}
View
1struct PolygonView: View {
2 @ObservedObject var polygonVm: PolygonViewModel
3
4 var body: some View {
5 VStack(spacing:40) {
6 Text("Polygon")
7 .font(.title)
8
9 ZStack {
10 RegularPolygon(sides: Int(polygonVm.sides))
11 .fill(polygonVm.solid ? polygonVm.shapeColor : Color.clear)
12 RegularPolygon(sides: Int(polygonVm.sides))
13 .stroke(polygonVm.shapeColor, lineWidth: 6.0)
14 }
15 .animation(.default)
16 .frame(width: 200, height: 200)
17
18 HStack(spacing:50) {
19 Spacer()
20 VStack {
21 Text("Sides \(polygonVm.sides, specifier: "%.0F" )")
22 Slider(value: $polygonVm.sides,
23 in: 3...20,
24 minimumValueLabel: Text("3"),
25 maximumValueLabel: Text("20")) {}
26
27 }
28 Spacer()
29 }
30
31 HStack(spacing:20) {
32 Spacer()
33
34 Button(polygonVm.solid ? "Outline" : "Solid") {
35 polygonVm.solid.toggle()
36 }
37 .buttonStyle(BlueButtonStyle())
38
39 Button("Increment Color") {
40 polygonVm.nextColor()
41 }
42 .buttonStyle(BlueButtonStyle())
43
44 Spacer()
45 }
46
47 Spacer()
48 }
49 }
50}
Main App modified to create instance of Polygon ViewModel when application is launched.
1@main
2struct PolygonApp: App {
3
4 var polygonVm = PolygonViewModel()
5
6 var body: some Scene {
7 WindowGroup {
8 PolygonView(polygonVm: polygonVm)
9 }
10 }
11}
Simple Polygon app implemented using MVVM
Save Polygon Data to UserDefaults
Only the Polygon Model needs to be updated to use UserDefaults in order to
persist the data in the app. The properties of PolygonModel
have the didSet
observer added, which is called immediately after the new value is set so the new
value is set on the UserDefaults. The default initialiser is updated to register keys
for the properties in UserDefaults and set the initial values for the struct.
1struct PolygonModel {
2 var sideNumber: Int {
3 didSet {
4 UserDefaults.standard.set(sideNumber, forKey: "polygon.sideNumber")
5 }
6 }
7
8 var solid: Bool {
9 didSet {
10 UserDefaults.standard.set(solid, forKey: "polygon.solid")
11 }
12 }
13
14 var colorIndex: Int {
15 didSet {
16 UserDefaults.standard.set(colorIndex, forKey: "polygon.colorIndex")
17 }
18 }
19
20 init() {
21 UserDefaults.standard.register(defaults: [
22 "polygon.sideNumber" : 4,
23 "polygon.solid" : true,
24 "polygon.colorIndex" : 3
25 ])
26
27 sideNumber = UserDefaults.standard.integer(forKey: "polygon.sideNumber")
28 solid = UserDefaults.standard.bool(forKey: "polygon.solid")
29 colorIndex = UserDefaults.standard.integer(forKey: "polygon.colorIndex")
30 }
31
32 fileprivate var colors:[Color] = [.red, .orange, .yellow, .green, .blue, .purple]
33
34 public var ShapeColor: Color {
35 colors[colorIndex % colors.count]
36 }
37
38 mutating func nextColor() {
39 self.colorIndex += 1
40 }
41}
There is one change required in the PolygonViewModel
as the creation of the
PolygonModel
no longer takes any parameters. The default values are now set within
the model.
1class PolygonViewModel: ObservableObject {
2 @Published private var polygon = PolygonModel()
3
4 var sides: Double {
5 get { Double(polygon.sideNumber) }
6 set { polygon.sideNumber = Int(newValue) }
7 }
8
9 var solid: Bool {
10 get { polygon.solid }
11 set { polygon.solid = newValue }
12 }
13
14 var shapeColor: Color {
15 get { return polygon.ShapeColor }
16 }
17
18 func nextColor() {
19 polygon.nextColor()
20 }
21}
Polygon data saved using UserDefaults
Conclusion
It can be very convenient to store small pieces of App data using UserDefaults. This is easy to use in a SwiftUI app. The designed purpose for UserDefaults is to store data related to user preferences, however it can be used to store any small set of data. In hooks in nicely to the MVVM design pattern where only the model needs to be modified to store and retrieve model data in UserDefaults.