UserDefaults: Saving values and objects locally in iOS apps

iOS apps have an in-built data dictionary for storing small amounts of data, mostly user settings and preferences.

These data persist across reboots and relaunches of the app.

UserDefaults is Key-Value storage. It stores Property List objects (String, Boolean, Integer, Date, Array, Dictionary and more) identified by String keys.

In this tutorial, we learn how to save simple values, arrays, dictionaries, as well as custom objects and lists of custom objects in UserDefaults.

Saving simple values

First, we initialise UserDefaults

let defaults = UserDefaults.standard

Here is how we save simple values

defaults.set(25, forKey: "days")
defaults.set(true, forKey: "isAdmin")
defaults.set("English", forKey: "Language")
defaults.set(Date(), forKey: "LastRun")

to retrieve these values

let days = defaults.integer(forKey: "days")

Similarly, for other data types, use the appropriate method.

For primitive data types, UserDefaults returns a default value if the key does not exist in the data dictionary.

  • integer(forKey:) returns an integer if the key exists, or 0 if it doesn’t.
  • bool(forKey:) returns a boolean if the key exists, or false if it doesn’t.
  • float(forKey:) returns a float if the key exists, or 0.0 if it doesn’t.
  • double(forKey:) returns a double if the key exists, or 0.0 if it doesn’t.

Saving an array

let array = ["Hello", "World"]
defaults.set(array, forKey: "myArray")

Saving a dictionary

let dict = ["blog": "finecoding", "author": "teknofreek"]
defaults.set(dict, forKey: "myDict")

Retrieving objects

When retrieving objects, the result is optional. If the key doesn’t exist in UserDefault, it returns nil.

So you either need to store it in an optional variable; or handle the missing value.

//retrieve array, init a new empty array if nil.
let mySavedArray = defaults.object(forKey: "myArray") as? [String] ?? [String]()

Saving custom objects

For saving custom objects to UserDefaults, our custom object must conform to NSCoding protocol, it declares two methods that your object must implement in order to be encoded and decoded.

Here’s how you define the object

import UIKit
class MyPlaceObj: NSObject, NSCoding {
    var name: String?
    var add: String?
    var lat: Double
    var lng: Double
    
    init(name: String, add: String, lat: Double, lng: Double) {
        self.name = name
        self.add = add
        self.lat = lat
        self.lng = lng
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
        self.add = aDecoder.decodeObject(forKey: "add") as? String ?? ""
        self.lat = aDecoder.decodeDouble(forKey: "lat")
        self.lng = aDecoder.decodeDouble(forKey: "lng")
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(lat, forKey: "lat")
        aCoder.encode(lng, forKey: "lng")
        aCoder.encode(name, forKey: "name")
        aCoder.encode(add, forKey: "add")
    }
}

To save this object, we will use NSKeyedArchiver, that encodes the object into an architecture-independent format that can be stored in a file.

let placeObj = MyPlaceObj.init(name: "some place name", add: "some address", lat: 25.3548, lng: 51.1839)
let placeData = NSKeyedArchiver.archivedData(withRootObject: placeObj)
defaults.set(placeData, forKey: "userlocation")

Retrieving custom object

guard let placeData = defaults.object(forKey: "userlocation") as? Data else { 
    return 
} 
// Use NSKeyedUnarchiver to convert Data / NSData back to Player object 
guard let userplace = NSKeyedUnarchiver.unarchiveObject(with: placeData) as? MyPlaceObj else { 
    return 
}

Saving an array of custom objects

For saving an array of custom objects, just make sure the object conforms to NSCoding protocol as shown above, and then save it the same way as you store a custom object.

//CREATE AN ARRAY
var placesArray: [MyPlaceObj] = []
placesArray.append(placeObj)
placesArray.append(placeObj)
placesArray.append(placeObj)
//SAVE IT
let placesDataNew = NSKeyedArchiver.archivedData(withRootObject: placesArray)
defaults.set(placesDataNew, forKey: "places")

Retrieving it

if let placesData = defaults.object(forKey: "places") as? NSData {
    placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as! [MyPlaceObj]
}

Saving custom objects using Codable

The Codable protocol introduced by Apple in Swift 4. It is a combination of

Encodable

 and 

Decodable

 protocol, so we don’t need to write lengthy methods.

So instead of our MyPlaceObj Object, we can simply define a struct since we don’t need conformation to NSObject anymore.

struct MyPlace : Codable {
    var name: String
    var add: String
    var lat: Double
    var lng: Double
}

Saving it

// Use PropertyListEncoder to convert MyPlace into Data / NSData 
defaults.set(try? PropertyListEncoder().encode(place), forKey: "place")

Retrieving it

guard let placeData = defaults.object(forKey: "place") as? Data else { return } 
// Use PropertyListDecoder to convert Data into MyPlace 
guard let place = try? PropertyListDecoder().decode(MyPlace.self, from: placeData) else { return }

 

 

 

 

 

 

 


Also published on Medium.

By |2019-04-02T17:10:02+00:00March 14th, 2019|Categories: iOS Tutorials|Tags: , , |0 Comments

Leave A Comment