How to use UserDefaults in SwiftUI

This tutorial will teach you how to use UserDefaults in SwiftUI. We are going to create a settings form view with several data entry controls such as text field, toggle, and a picker. Each of the controls will read and write its values from and to the UserDefaults database.

If you’d like to learn more about UserDefaults or creating forms in SwiftUI on their own, then take a look at our previous, introductory tutorials covering UserDefaults and Forms. Otherwise, keep on reading.

In order to accomplish the goal of this tutorial, we’re going to use @ObservedObject property wrapper inside of the main view to store an instance of custom UserSettings class, which will conform to the ObservableObject protocol and act as a data store for form data.

Basic Form with TextField saved in UserDefaults

First, create a single view iOS app using SwiftUI.

Then, create a new Swift File in your Xcode project and call it UserSettings.swift. Inside the new file, implement a class called UserSettings, conforming to the ObservableObject, with one @Published String variable holding username from the UI form. Remember to import Combine framework.

import Foundation
import Combine

class UserSettings: ObservableObject {
    @Published var username: String {
        didSet {
            UserDefaults.standard.set(username, forKey: "username")
        }
    }
    
    init() {
        self.username = UserDefaults.standard.object(forKey: "username") as? String ?? ""
    }
}

Upon initialization, an instance of the UserSettings class reads a value of username from UserDefaults and if it can’t find a value associated with the given key, returns an empty string.

Furthermore, whenever a value of @Published username changes, it’s being saved to UserDefaults.

Now, let’s switch over to ContentView to create an @ObservedObject instance of UserSettings class and bind the username variable to a TextField inside of the form:

struct ContentView: View {
    @ObservedObject var userSettings = UserSettings()
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("PROFILE")) {
                    TextField("Username", text: $userSettings.username)
                }
            }
            .navigationBarTitle("Settings")
        }
    }
}

Build and run your project. Type in a username into the text field, force quit the app and re-open it – the username should be preserved. The result should look like this:

SwiftUI Form with TextField backed by UserDefaults
SwiftUI Form with TextField backed by UserDefaults

Form with Toggle saved in UserDefaults

Next up, let’s add a toggle to the settings form which will control whether our account is private or not. In UserSettings class, add the following @Published boolean variable:

@Published var isPrivate: Bool {
    didSet {
        UserDefaults.standard.set(isPrivate, forKey: "isAccountPrivate")
    }
}

And add the following line to its initializer:

self.isPrivate = UserDefaults.standard.object(forKey: "isAccountPrivate") as? Bool ?? true

By default, we’re going to keep user account private.

In ContentView, update the form with the following code which adds a toggle to it and binds its value to the isPrivate @Published variable from the instance of UserSettings:

Form {
    Section(header: Text("PROFILE")) {
        TextField("Username", text: $userSettings.username)
        Toggle(isOn: $userSettings.isPrivate) {
            Text("Private Account")
        }
    }
}

Build and run your project. It should look like this:

SwiftUI Form with Toggle backed by UserDefaults
SwiftUI Form with Toggle backed by UserDefaults

Form with Picker saved in UserDefaults

Finally, let’s add a picker to the settings form. The picker will allow users to pick a ringtone.

Add a list of available ringtone names to the UserSettings class, provide a @Published ringtone variable and a proper initialization code. The complete UserSettings code for this tutorial should look like this:

import Foundation
import Combine

class UserSettings: ObservableObject {
    @Published var username: String {
        didSet {
            UserDefaults.standard.set(username, forKey: "username")
        }
    }
    
    @Published var isPrivate: Bool {
        didSet {
            UserDefaults.standard.set(isPrivate, forKey: "isAccountPrivate")
        }
    }
    
    @Published var ringtone: String {
        didSet {
            UserDefaults.standard.set(ringtone, forKey: "ringtone")
        }
    }
    
    public var ringtones = ["Chimes", "Signal", "Waves"]
    
    init() {
        self.username = UserDefaults.standard.object(forKey: "username") as? String ?? ""
        self.isPrivate = UserDefaults.standard.object(forKey: "isAccountPrivate") as? Bool ?? true
        self.ringtone = UserDefaults.standard.object(forKey: "ringtone") as? String ?? "Chimes"
    }
}

Add a picker to the settings form which iterates over ringtones array from UserSettings instance and binds its selection to the ringtone @Published variable.

Complete ContentView code for this tutorial should look like this:

struct ContentView: View {
    @ObservedObject var userSettings = UserSettings()
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("PROFILE")) {
                    TextField("Username", text: $userSettings.username)
                    Toggle(isOn: $userSettings.isPrivate) {
                        Text("Private Account")
                    }
                    Picker(selection: $userSettings.ringtone, label: Text("Ringtone")) {
                        ForEach(userSettings.ringtones, id: \.self) { ringtone in
                            Text(ringtone)
                        }
                    }
                }
            }
            .navigationBarTitle("Settings")
        }
    }
}

Final app should look like this:

SwiftUI Form backed by UserDefaults
SwiftUI Form backed by UserDefaults

Related tutorials:

Take a look at Apple’s official documentation of UserDefaults and Form.