Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save Struct to 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?

like image 576
Jacob Cavin Avatar asked Jul 03 '17 00:07

Jacob Cavin


People also ask

Can I save an object in UserDefaults?

UserDefaults can simply save custom object which conforms to Codable protocol. JSONEncoder and JSONDecoder play important roles for handling the data transformation. Nested object must also conform to Codable protocol in order to encode and decode successfully.

Can we store image in UserDefaults?

It is, but it isn't possible to store an image as is in the user's defaults database. The defaults system only supports strings, numbers, Date objects, and Data objects. This means that you need to convert the image to a Data object before you can store it in the user's defaults database.


2 Answers

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) } 
like image 114
matt Avatar answered Sep 20 '22 11:09

matt


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)     } } 
like image 30
YannSteph Avatar answered Sep 23 '22 11:09

YannSteph