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:
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()