UserDefaults for storing data in iOS iOS 01.09.2019

Your application will need a way to store this information, retrieve it, and possibly search and sort this data. Working with data can sometimes be difficult. Fortunately, Apple has provided methods and frameworks to make this process easier.

There are some things to consider when deciding where to store certain kinds of information. The easiest way to store information is within the preferences file, but this method has some downsides:

  • All of the data is both read and written at the same time. If you are going to be writing often or writing and reading large amounts of data, this could take time and slow down your application. As a general rule, your preferences file should never be larger than 100 KB. If your preferences file starts to become larger than 100 KB, consider using Core Data as a way to store your information.
  • The preferences file does not provide many options when it comes to searching and ordering information.

The preferences file is really nothing more than a standardized XML file with accompanying classes and methods to store application-specific information. A preference would be, for example, the sorting column and direction (ascending/ descending) of a list. Anything that is generally customizable within an app should be stored in a preferences file.

Sensitive data should not be stored in the preferences file or in a database without additional encryption. Luckily, Apple provides a way to store sensitive information. It is called the keychain.

Apple has provided developers with the UserDefaults class; this class makes it easy to read and write preferences for iOS, macOS, tvOS, and watchOS. The great thing is that, in this case, you can use the same code for iOS, macOS, and tvOS. The only difference between the several implementations is the location of the preferences file.

On iOS, the preferences file is located in your application’s container in the /Library/Preferences folder.

All you need to do to write preferences is to create a UserDefaults object. This is done with the following line:

var prefs: UserDefaults = UserDefaults.standard

This instantiates the prefs object so you can use it to set preference values. Next, you need to set the preference keys for the values that you want to save.

Once you have instantiated the object, you can just call set(_:forKey:) to set an object. If you wanted to save the username of sherlock.holmes, you would call the following line of code:

prefs.set("sherlock.holmes", forKey: "username")

After a certain period of time, your app will automatically write changes to the preferences file. You can force your app to save the preferences by calling the synchronize function, but this should only be used if you cannot wait for the next synchronization interval such as if your app is immediately going to exit. To call the synchronize function, you would write the following line:

prefs.synchronize()

With just three lines of code, you are able to create a preference object, set a preference value, and write the preferences file. It is an easy and clean process. Here is all of the code:

let prefs: UserDefaults = UserDefaults.standard
prefs.set("sherlock.holmes", forKey: "username")
prefs.set(10, forKey: "booksInList")
prefs.synchronize()

Reading preferences is similar to writing preferences. Just like with writing, the first step is to obtain the UserDefaults object. This is done in the same way as it was done in the writing process:

var prefs: UserDefaults = UserDefaults.standard

Now that you have the object, you are able to access the preference values that are set. For writing, you use the set syntax; for reading, you use the string(forKey:) method. You use the string(forKey:) method because the value you put in the preference was a String. In the writing example, you set preferences for the username and for the number of books in the list to display. You can read those preferences by using the following simple lines of code:

let username = prefs.string(forKey: "username")
let booksInList = prefs.integer(forKey: "booksInList")

Set the value of "favoriteFruits" key to an array of strings:

let fruits = ["Apple", "Banana", "Mango"]
defaults.set(fruits, forKey: "favoriteFruits")

Get the array of strings associated with the "favoriteFruits" key:

let favoriteFruits = defaults.array(forKey: "favoriteFruits”)

Get the dictionary associated with the "toggleStates" key:

let toggleStates = defaults.dictionary(forKey: "toggleStates")

Set the value of "toggleStates" key to a Swift dictionary:

let toggleStates = ["WiFi": true, "Bluetooth": false, "Cellular": true]
defaults.set(toggleStates, forKey: "toggleStates")

You can check if there is a value usung following snippet

extension UserDefaults {
    static func contains(_ key: String) -> Bool {
        return UserDefaults.standard.object(forKey: key) != nil
    }
}

Providing a default value in absence of a default

You have to be careful when using other get methods because some of them might surprise you. For example, when using the integer(forKey:) method, it returns 0 in absence of the specified key.

In order to unify this behavior across your app, you can always use the object(forKey:) method when reading data from UserDefaults. It guarantees you to return nil in case the given key is absent.

Let’s take a look at an example of using a nil coalescing operator in Swift to set a default value in case the given key doesn’t exist:

let favoriteFruits = defaults.object(forKey: "favoriteFruits") as? [String] ?? [String]()

UserDefaults has a register(defaults:) method. To use register(defaults:), you need to specify a dictionary of preference keys and their default value. In the following example, we set both enabledSound and enabledVibration a default value of true.

let userDefaults = UserDefaults.standard
userDefaults.register(
    defaults: [
        "enabledSound": true,
        "enabledVibration": true
    ]
)

userDefaults.bool(forKey: "enabledSound") // true

Save custom objects into UserDefaults using Codable

iOS supports several types of objects that we can directly save into UserDefaults like Int, String, Float, Double, Bool, URL, Data or collection of these types.

Structs can’t be directly stored in UserDefaults because UserDefaults does not know how to serialize it. As UserDefaults is backed by plist files, struct needs to be converted representation supported by it.

According to Apple: If you want to store any other type of object, you should typically archive it to create an instance of NSData.

extension UserDefaults {
    func setObject<T: Codable>(_ value: T?, forKey defaultName: String){
        let data = try? JSONEncoder().encode(value)
        set(data, forKey: defaultName)
    }

    open func getObject<T>(_ type: T.Type, forKey defaultName: String) -> T? where T : Decodable {
        guard let encodedData = data(forKey: defaultName) else {
            return nil
        }

        return try! JSONDecoder().decode(type, from: encodedData)
    }
}

Model

struct Movie: Codable {
    var title: String
    var year: Int 
}

Usage

let movieKey = "MOVIE_KEY"
UserDefaults.standard.setObject(Movie(title: "Movie", year: 2020), forKey: movieKey)
if let movie = UserDefaults.standard.getObject(Movie.self, forKey: movieKey) {
    print("Movie: \(movie)")
}

How to clear UserDefaults ?

UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
UserDefaults.standard.synchronize()