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?
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.
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.
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) }
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) } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With