Save Struct to UserDefaults

save object in userdefaults swift 4
save struct in userdefaults swift 5
save object in userdefaults swift 5
save codable to userdefaults
swift codable userdefaults
save custom object to nsuserdefaults swift 4
save array of struct in userdefaults swift
store custom struct in userdefaults

I have a struct that I want to save to UserDefaults. Here's my struct

struct Song {
    var title: String
    var artist: String
}

var songs: [Song] = [
    Song(title: "Title 1", artist "Artist 1"),
    Song(title: "Title 2", artist "Artist 2"),
    Song(title: "Title 3", artist "Artist 3"),
]

In another ViewController, I have a UIButton that appends to this struct like

@IBAction func likeButtonPressed(_ sender: Any) {   
   songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))
}

I want it so that whenever the user clicks on that button also, it saves the struct to UserDefaults so that whenever the user quits the app and then opens it agian, it is saved. How would I do this?

In Swift 4 this is pretty much trivial. Make your struct codable simply by marking it as adopting the Codable protocol:

struct Song:Codable {
    var title: String
    var artist: String
}

Now let's start with some data:

var songs: [Song] = [
    Song(title: "Title 1", artist: "Artist 1"),
    Song(title: "Title 2", artist: "Artist 2"),
    Song(title: "Title 3", artist: "Artist 3"),
]

Here's how to get that into UserDefaults:

UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")

And here's how to get it back out again later:

if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
    let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}

How to load and save a struct in UserDefaults using Codable, struct Person: Codable { var name: String } let taylor = Person(name: "Taylor Swift​"). To save that to UserDefaults you must first encode it as  Either you can save each property of your struct one by one into UserDefaults (which is a very hectic task to do, definitely not recommended), or you can use any third-party library, like

This is my UserDefaults extension in main thread, to set get Codable object into UserDefaults

// MARK: - UserDefaults extensions

public extension UserDefaults {

    /// Set Codable object into UserDefaults
    ///
    /// - Parameters:
    ///   - object: Codable Object
    ///   - forKey: Key string
    /// - Throws: UserDefaults Error
    public func set<T: Codable>(object: T, forKey: String) throws {

        let jsonData = try JSONEncoder().encode(object)

        set(jsonData, forKey: forKey)
    }

    /// Get Codable object into UserDefaults
    ///
    /// - Parameters:
    ///   - object: Codable Object
    ///   - forKey: Key string
    /// - Throws: UserDefaults Error
    public func get<T: Codable>(objectType: T.Type, forKey: String) throws -> T? {

        guard let result = value(forKey: forKey) as? Data else {
            return nil
        }

        return try JSONDecoder().decode(objectType, from: result)
    }
}

Update This is my UserDefaults extension in background, to set get Codable object into UserDefaults

// MARK: - JSONDecoder extensions

public extension JSONDecoder {

    /// Decode an object, decoded from a JSON object.
    ///
    /// - Parameter data: JSON object Data
    /// - Returns: Decodable object
    public func decode<T: Decodable>(from data: Data?) -> T? {
        guard let data = data else {
            return nil
        }
        return try? self.decode(T.self, from: data)
    }

    /// Decode an object in background thread, decoded from a JSON object.
    ///
    /// - Parameters:
    ///   - data: JSON object Data
    ///   - onDecode: Decodable object
    public func decodeInBackground<T: Decodable>(from data: Data?, onDecode: @escaping (T?) -> Void) {
        DispatchQueue.global().async {
            let decoded: T? = self.decode(from: data)

            DispatchQueue.main.async {
                onDecode(decoded)
            }
        }
    }
}

// MARK: - JSONEncoder extensions  

public extension JSONEncoder {

    /// Encodable an object
    ///
    /// - Parameter value: Encodable Object
    /// - Returns: Data encode or nil
    public func encode<T: Encodable>(from value: T?) -> Data? {
        guard let value = value else {
            return nil
        }
        return try? self.encode(value)
    }

    /// Encodable an object in background thread
    ///
    /// - Parameters:
    ///   - encodableObject: Encodable Object
    ///   - onEncode: Data encode or nil
    public func encodeInBackground<T: Encodable>(from encodableObject: T?, onEncode: @escaping (Data?) -> Void) {
        DispatchQueue.global().async {
            let encode = self.encode(from: encodableObject)

            DispatchQueue.main.async {
                onEncode(encode)
            }
        }
    }
}       

// MARK: - NSUserDefaults extensions

public extension UserDefaults {

    /// Set Encodable object in UserDefaults
    ///
    /// - Parameters:
    ///   - type: Encodable object type
    ///   - key: UserDefaults key
    /// - Throws: An error if any value throws an error during encoding.
    public func set<T: Encodable>(object type: T, for key: String, onEncode: @escaping (Bool) -> Void) throws {

        JSONEncoder().encodeInBackground(from: type) { [weak self] (data) in
            guard let data = data, let `self` = self else {
                onEncode(false)
                return
            }
            self.set(data, forKey: key)
            onEncode(true)
        }
    }

    /// Get Decodable object in UserDefaults
    ///
    /// - Parameters:
    ///   - objectType: Decodable object type
    ///   - forKey: UserDefaults key
    ///   - onDecode: Codable object
    public func get<T: Decodable>(object type: T.Type, for key: String, onDecode: @escaping (T?) -> Void) {
        let data = value(forKey: key) as? Data
        JSONDecoder().decodeInBackground(from: data, onDecode: onDecode)
    }
}

How To Save and Load Structs From UserDefaults in Swift, The thought of saving a struct in UserDefaults might have crossed your mind while dealing with information related to users or any other  The considered saving a struct in UserDefaults would possibly have crossed your thoughts whilst coping with data associated with customers or some other delicate data that you just get out of your API calls on your iOS application.

If the struct contains only property list compliant properties I recommend to add a property propertyListRepresentation and a corresponding init method

struct Song {

    var title: String
    var artist: String

    init(title : String, artist : String) {
        self.title = title
        self.artist = artist
    }

    init?(dictionary : [String:String]) {
        guard let title = dictionary["title"],
            let artist = dictionary["artist"] else { return nil }
        self.init(title: title, artist: artist)
    }

    var propertyListRepresentation : [String:String] {
        return ["title" : title, "artist" : artist]
    }
}

To save an array of songs to UserDefaults write

let propertylistSongs = songs.map{ $0.propertyListRepresentation }
UserDefaults.standard.set(propertylistSongs, forKey: "songs")

To read the array

if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] {
    songs = propertylistSongs.flatMap{ Song(dictionary: $0) }
}

If title and artist will never be mutated consider to declare the properties as constants (let) .


This answer was written while Swift 4 was in beta status. Meanwhile conforming to Codable is the better solution.

Save and load structs and struct arrays in UserDefaults, By default Swift's UserDefaults doesn't provide a method for directly storing and loading structs and struct arrays. With Swift 4, Apple unveiled a brand new way of data encoding and decoding. Swift 4 introduced the Codable protocol. It provides support for class, struct and enum. struct Person: Codable { var name: String } let taylor = Person(name: "Taylor Swift") To save that to UserDefaults you must first encode it as JSON using JSONEncoder, which will send back a Data instance you can send straight to UserDefaults.

If you are just trying to save this array of songs in UserDefaults and nothing fancy use this:-

//stores the array to defaults
UserDefaults.standard.setValue(value: songs, forKey: "yourKey")

//retrieving the array

UserDefaults.standard.object(forKey: "yourKey") as! [Song]
//Make sure to typecast this as an array of Song

If you are storing a heavy array, I suggest you to go with NSCoding protocol or the Codable Protocol in swift 4

Example of coding protocol:-

 struct Song {
        var title: String
        var artist: String
    }

    class customClass: NSObject, NSCoding { //conform to nsobject and nscoding

    var songs: [Song] = [
        Song(title: "Title 1", artist "Artist 1"),
        Song(title: "Title 2", artist "Artist 2"),
        Song(title: "Title 3", artist "Artist 3"),
    ]

    override init(arr: [Song])
    self.songs = arr
    }

    required convenience init(coder aDecoder: NSCoder) {
    //decoding your array
    let songs = aDecoder.decodeObject(forKey: "yourKey") as! [Song]

    self.init(are: songs)
    }

    func encode(with aCoder: NSCoder) {
    //encoding
    aCoder.encode(songs, forKey: "yourKey")
    }

}

save array of struct to UserDefaults · GitHub, save array of struct to UserDefaults. GitHub Gist: instantly share code, notes, and snippets. All iOS apps have a built in data dictionary that stores small amounts of user settings for as long as the app is installed. This system, called UserDefaults can save integers, booleans, strings, arrays, dictionaries, dates and more, but you should be careful not to save too much data because it will slow the launch of your app.

From here:

A default object must be a property list—that is, an instance of (or for collections, a combination of instances of): NSData , NSString , NSNumber , NSDate , NSArray , or NSDictionary . If you want to store any other type of object, you should typically archive it to create an instance of NSData.

You need to use NSKeydArchiver. Documentation can be found here and examples here and here.

Storing struct in UserDefaults – Augmented Code, It should be noted that we could just store data in user defaults. But as data is not human-readable, let's convert it to Dictionary instead. You will see updates in your activity feed. You may receive emails, depending on your notification preferences. I have a problem in saving Struct format in .mat file. Any way that I can save it as a .mat file of 1x5? which consists of 947 value in one mat file. but doesn't work!!! Sign in to answer this question.

Save custom object / type into UserDefaults using Codable, If you have tried to save custom object into UserDefaults before, you might get an only class can be conformed to NSCoding, struct can't. Building on the previous video, we add a table of our tasks and update the table after new tasks are added.

I have a struct that I want to save to UserDefaults. Here's my struct struct Song { var title: String var artist: String } var songs: [Song] = [ Song(title: "Title 1", artist  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.

Unfortunately to save your struct into either data store, you will need to convert the struct to a dictionary, because structs aren't accepted as a data type by either store. That means you will also need to create an initializer for each struct that accepts the dictionary version of the struct. If you're able

Comments
  • Check stackoverflow.com/questions/28916535/…
  • If you are trying for swift 4. There is new protocol 'Codable' which is great for this kinda stuff. For lesser swift version, you have to create dictionary for your struct and manually parse the data
  • getting error that doesn't confirm to protocol codable
  • @Paragon: you have to implement the func encode(to encoder: Encoder) method in your struct and then do something like func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(artist, forKey: . artist) }
  • One thing I found with this is if your struct changes (e.g. you add a new field) and you try to get it form userdefaults you will get nil. So that is one draw back.
  • @Micro And that's correct behavior. It has nothing to do with this answer! If the type no longer matches the type that was stored in user defaults, it shouldn't be possible to pull it out of user defaults; the old type literally no longer exists. That's just a feature of you developing the app bit by bit; it has nothing to do with the question or answer here.
  • @matt Just pointing this out incase someone uses this as a user object in their app. If it changes, the user will no longer be accessible. Bug? Feature? YOU DECIDE!
  • I used it as class func getUser() -> User? { UserDefaults.standard.get(object: User.self, for: DefaultKeys.user) { user in return user } return nil } But it gives me a warning Expression of type 'User?' is unused when returning user value
  • @EICaptainv2.0 Yes because is optional
  • So, what to do to get rid of the warning. Warning stay even if I wrapped the return value Expression of type 'User' is unused