NEW: Nominations are now open for the 2019 Swift Community Awards! >>

Storing user settings with UserDefaults

Paul Hudson    @twostraws   

Arguably the biggest difference between a website and an app is their approach to user data. On one hand, websites do their best to invade privacy by tracking cookies, serving up remarketing adverts, and watching every move we make, so very few users want to trust them with more data. On the other hand, we pretty much expect apps to store our data – we want them to, and it would be odd if every app launched with a GDPR “can we serve you cookies?” notice.

So, it’s no surprise that iOS gives us several ways to read and write user data, and I want to look at two of them here.

The first is called UserDefaults, and it allows us to store small amount of user data directly attached to our app. There is no specific number attached to “small”, but you should keep in mind that everything you store in UserDefaults will automatically be loaded when your app launches – if you store a lot in there your app launch will slow down. To give you at least an idea, you should aim to store no more than 512KB in there.

Tip: If you’re thinking “512KB? How much is that?” then let me give you a rough estimate: it’s about as much text as all the chapters you’ve read in this book so far.

UserDefaults is perfect for storing user settings and other important data – you might track when the user last launched the app, which news story they last read, or other passively collected information.

However, there is a catch: it is stringly typed. This is a bit of a joke name, because “strongly typed” means a type-safe language like Swift where each constant and variable has a specific type such as Int or String, but “stringly typed” means some code that uses strings in places where they might cause problems.

Enough chat – let’s look at some code. Here’s a view with a button that shows a tap count, and increments that count every time the button is tapped:

struct ContentView: View {
    @State private var tapCount = 0

    var body: some View {
        Button("Tap count: \(tapCount)") {
            self.tapCount += 1
        }
    }
}

As this is clearly A Very Important App, we want to save the number of taps that the user made, so when they come back to the app in the future they can pick up where they left off.

Well, making that happen only takes two changes. First, we need to write the tap count to UserDefaults whenever it changes, so add this after the line self.tapCount += 1:

UserDefaults.standard.set(self.tapCount, forKey: "Tap")

In just that single line of code you can see three things in action:

  1. We need to use UserDefaults.standard. This is the built-in instance of UserDefaults that is attached to our app, but in more advanced apps you can create your own instances. For example, if you want to share defaults across several app extensions you might create your own UserDefaults instance.
  2. There is a single set() method that accepts any kind of data – integers, Booleans, strings, and more.
  3. We attach a string name to this data, in our case it’s the key “Tap”. This key is case-sensitive just like regular Swift strings, and it’s important – we need to use the same key to read the data back out of UserDefaults.

Speaking of reading the data back, rather than start with tapCount set to 0 we should instead make it read the value back from UserDefaults like this:

@State private var tapCount = UserDefaults.standard.integer(forKey: "Tap")

Notice how that uses exactly the same key name, which ensures it reads the same integer value.

Go ahead and give the app a try and see what you think – you ought to be able tap the button a few times, go back to Xcode, run the app again, and see the number exactly where you left it.

There are two things can’t see in that code, but both matter. First, what happens if we don’t have the “Tap” key set? This will be the case the very first time the app is run, but as you just saw it works fine – if the key can’t be found it just sends back 0.

Sometimes having a default value like 0 is helpful, but other times it can be confusing. With Booleans, for example, you get back false if boolean(forKey:) can’t find the key you asked for, but is that false a value you set yourself, or does it mean there was no value at all?

Second, it takes iOS a little time to write your data to permanent storage – to actually save that change to the device. They don’t write updates immediately because you might make several back to back, so instead they wait some time then write out all the changes at once. How much time is another number we don’t know, but a couple of seconds ought to do it.

As a result of this, if you tap the button then quickly relaunch the app from Xcode, you’ll find your most recent tap count wasn’t saved. There used to be a way of forcing updates to be written immediately, but at this point it’s worthless – even if the user immediately started the process of terminating your app after making a choice, your defaults data would be written immediately so nothing will be lost.

SAVE 20% ON iOS CONF SG The largest iOS conference in Southeast Asia is back in Singapore for the 5th time in January 2020, now with two days of workshops plus two days of talks on SwiftUI, Combine, GraphQL, and more! Save a massive 20% on your tickets by clicking on this link.

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5