JsonEncoder always return empty array - ios

Im trying to save struct array into UserDefaults and I cant figure out why JsonEncoder return empty data. I have setup model that conforms Codable protocol
struct MenuItem : Codable{
let name : String?
let icon : String?
init(name : String?, icon : String?){
self.name = name
self.icon = icon
}
}
and also created Defaults manager for saving it into user defaults.
class DefaultsManager {
static let shared = DefaultsManager()
init(){}
var items: [MenuItem]{
get{
if let json = UserDefaults.standard.data(forKey: "key"){
return decodeFromJson(jsonData: json)
} else {
return []
}
}
set{
let json = codeToJson(data: items)
UserDefaults.standard.set(json, forKey: "key")
}
}
fileprivate func codeToJson<T:Codable>(data: Array<T>) -> Data?{
do {
return try JSONEncoder().encode(data)
} catch {
print(error)
return nil
}
}
fileprivate func decodeFromJson<T:Codable>(jsonData: Data) -> [T]{
do {
return try JSONDecoder().decode(Array<T>.self, from: jsonData)
} catch {
print(error)
return []
}
}
}
but whatever I do JsonEncoder returns empty data.. I tried to google but without success.

That's a very common mistake.
In a setter of a computed property the new value is represented by the implicit newValue variable, it's not the property itself.
set {
let json = codeToJson(data: newValue)
UserDefaults.standard.set(json, forKey: "key")
}

Change Array<T>.self to [MenuItem].self
return try JSONDecoder().decode([MenuItem].self, from: jsonData)

Related

Userdefaults can not retrieve custom Struct in Share Extension

I have an App and also a Share Extension. Between them I share data via UserDefaults. But it stopped working all of a sudden. Only bools or Strings can now be retrieved inside the Share Extension but when trying to retrieve a Custom Struct it is always returning nil.
Custom Struct getter/setter in UserDefaults:
//MARK: dataSourceArray
func setDataSourceArray(data: [Wishlist]?){
set(try? PropertyListEncoder().encode(data), forKey: Keys.dataSourceKey)
synchronize()
}
func getDataSourceArray() -> [Wishlist]? {
if let data = self.value(forKey: Keys.dataSourceKey) as? Data {
do {
_ = try PropertyListDecoder().decode(Array < Wishlist > .self, from: data) as [Wishlist]
} catch let error {
print(error)
}
if let dataSourceArray =
try? PropertyListDecoder().decode(Array < Wishlist > .self, from: data) as[Wishlist] {
return dataSourceArray
}
}
return nil
}
I am calling it like this inside my Extension as well as in my Main App:
if let defaults = UserDefaults(suiteName: UserDefaults.Keys.groupKey) {
if let data = defaults.getDataSourceArray() {
print("working")
} else {
print("error getting datasourceArray")
}
}
This is printing "working" in the Main App but "error getting datasourceArray" in my Extension. I don't understand the issue, especially because simple Bool-Getter are working also from my Share Extension, the issue is only with the Custom Struct.
What am I missing here?
Wishlist Struct:
import UIKit
enum PublicState: String, Codable {
case PUBLIC
case PUBLIC_FOR_FRIENDS
case NOT_PUBLIC
}
struct Wishlist: Codable {
var id: String
var name: String
var image: UIImage
var wishes: [Wish]
var color: UIColor
var textColor: UIColor
var index: Int
var publicSate: PublicState
enum CodingKeys: String, CodingKey {
case id, name, image, wishData, color, textColor, index, isPublic, isPublicForFriends, publicSate
}
init(id: String, name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int, publicSate: PublicState) {
self.id = id
self.name = name
self.image = image
self.wishes = wishes
self.color = color
self.textColor = textColor
self.index = index
self.publicSate = publicSate
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
name = try values.decode(String.self, forKey: .name)
wishes = try values.decode([Wish].self, forKey: .wishData)
color = try values.decode(Color.self, forKey: .color).uiColor
textColor = try values.decode(Color.self, forKey: .textColor).uiColor
index = try values.decode(Int.self, forKey: .index)
publicSate = try values.decode(PublicState.self, forKey: .publicSate)
let data = try values.decode(Data.self, forKey: .image)
guard let image = UIImage(data: data) else {
throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
}
self.image = image
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(wishes, forKey: .wishData)
try container.encode(Color(uiColor: color), forKey: .color)
try container.encode(Color(uiColor: textColor), forKey: .textColor)
try container.encode(index, forKey: .index)
try container.encode(image.pngData(), forKey: .image)
try container.encode(publicSate, forKey: .publicSate)
}
}
Update
This is the part where it fails:
if let data = self.value(forKey: Keys.dataSourceKey) as? Data
Is there any way to catch an error?
I also found out that this feature is actually working for other users. The app is live: https://apps.apple.com/de/app/wishlists-einfach-w%C3%BCnschen/id1503912334
But it is not working for me? I deinstalled the app, downloaded it from the App Store but it is still not working.
I had the same problem but with another type of extension. Hope it works for you too.
Create a file you share between the two targets and put the following code there:
//MARK: - Model
struct WishlistStruct: Codable {
//your wishlist struct, I'll assume you'll have a name and some items
var name : String
var items : [String]
}
typealias Wishlist = WishlistStruct
//MARK: - Defaults
let sharedUserdefaults = UserDefaults(suiteName: SharedDefault.suitName)
struct SharedDefault {
static let suitName = "yourAppGroupHere"
struct Keys{
static let WishlistKey = "WishlistKey"
}
}
var myWishlist: [Wishlist] {
get {
if let data = sharedUserdefaults?.data(forKey: SharedDefault.Keys.WishlistKey) {
let array = try! PropertyListDecoder().decode([Wishlist].self, from: data)
return array
} else{
//Here you should return an error but I didn't find any way to do that so I put this code which hopefully will never be executed
return sharedUserdefaults?.array(forKey: SharedDefault.Keys.WishlistKey) as? [Wishlist] ?? [Wishlist]()
}
} set {
}
}
Now, whenever you need to retrieve the struct, both in app and extension, use the following code:
var wishlist : [Wishlist] = []
var currentWishlist = myWishlist
//In your viewDidLoad call
wishlist.append(contentsOf: myWishlist)
To edit the data inside of your wishlist use the following code
wishlist.append(Wishlist(name: "wishlist", items: ["aaa","bbb","ccc"]))
currentWishlist.append(Wishlist(name: "wishlist", items: items: ["aaa","bbb","ccc"]))
if let data = try? PropertyListEncoder().encode(currentWishlist) {
sharedUserdefaults?.set(data, forKey: SharedDefault.Keys.WishlistKey)
}
Let me know if you need more clarifications
Updated code to your struct. You should change some type of properties to yours(i remove some field for test).
import UIKit
enum PublicState: String, Codable {
case PUBLIC
case PUBLIC_FOR_FRIENDS
case NOT_PUBLIC
}
struct Wishlist: Codable {
var id: String = ""
var name: String = ""
var image: Data = Data()//TODO: use Data type
var color: String = ""//TODO: change it to your class
// var wish: //TODO: add this filed, i don't have it
var textColor: String = "" //TODO: change it to your class
var index: Int = 0
var publicSate: PublicState = .PUBLIC
enum CodingKeys: String, CodingKey {
case id, name, image, color, textColor, index, publicSate
}
init() {}
init(id: String, name: String, image: Data, color: String, textColor: String, index: Int, publicSate: PublicState) {
self.id = id
self.name = name
self.image = image
self.color = color
self.textColor = textColor
self.index = index
self.publicSate = publicSate
}
}
struct WishlistContainer: Codable {
var list: [Wishlist] = []
enum CodingKeys: String, CodingKey {
case list
}
}
class UserDefaultsManager {
//be sure your correctly setup your app groups
private var currentDefaults: UserDefaults = UserDefaults(suiteName: "put here your app group ID")!
private func getFromLocalStorage<T: Codable>(model: T.Type, key: String) -> T? {
if let decoded = currentDefaults.object(forKey: key) as? String {
guard let data = decoded.data(using: .utf8) else { return nil }
if let product = try? JSONDecoder().decode(model.self, from: data) {
return product
}
}
return nil
}
private func saveToLocalStorage(key: String, encodedData: String) {
currentDefaults.set(encodedData, forKey: key)
}
private func removeObject(key: String) {
currentDefaults.removeObject(forKey: key)
}
var wishList: WishlistContainer? {
set {
guard let value = newValue else {
removeObject(key: "wishList")
return
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let jsonData = try? encoder.encode(value) else { return }
guard let jsonString = String(data: jsonData, encoding: .utf8) else { return }
saveToLocalStorage(key: "wishList", encodedData: jsonString)
}
get {
guard let value = getFromLocalStorage(model: WishlistContainer.self, key: "wishList") else {
return nil
}
return value
}
}
}
//MARK: - Usage
let list: [Wishlist] = [Wishlist()]
let container: WishlistContainer = WishlistContainer(list: list)
UserDefaultsManager().wishList = container //set
UserDefaultsManager().wishList // get

Swift generics. How to check if the type of Array's Element inherits from other class

I want to determine in my method whether the generic type is an array and if so, read some static variable of the type that is the Element of the array.
I thought that this would work:
print(T.self)
print(T.self is Array<SPTBaseObject>.Type)
if let A = T.self as? Array<SPTBaseObject>.Type {
print(A.Element.pluralKey)
}
But the output from the console reads:
Array<SPTArtist>
false
Changing the second line to
print(T.self is Array<SPTArtist>.Type)
Prints true, but it seems pointless to check against every possible type. I just want it to return true when the Element is a class inheriting from the SPTBaseObject.
The SPTArtist is a class inheriting from the SPTBaseObject.
public class SPTArtist: SPTBaseObject {
...
}
public class SPTBaseObject: Codable {
internal class var pluralKey: String? {
return nil
}
}
EDIT:
My method looks like this
private class func perform<T>(request: URLRequestConvertible, completion: #escaping (Result<T, Error>) -> Void) where T: Decodable {
AF.request(request).responseData { response in
if let error = response.error {
completion(.failure(error))
}
guard let data = response.value else {
completion(.failure(SPTError.noDataReceivedError))
return
}
if let error = try? JSONDecoder().decode(SPTErrorResponse.self, from: data) {
completion(.failure(error.error))
return
}
// Check if T is an array, if so try to decode a Root<T> object. Here's an error, I cannot construct the Root type like this although the Element is guaranteed to be a SPTBaseObject subclass
if let A = T.self as? Array<SPTBaseObject>.Type,
let root = try? JSONDecoder().decode(Root<A.Element.self>, from: data) {
completion(.success(root.items))
} else if let object = try? JSONDecoder().decode(T.self, from: data) {
completion(.success(object))
} else {
completion(.failure(SPTError.decodingError))
}
}
}
The Root class which will utilize the pluralKey property of SPTBaseObject
class Root<T>: Decodable where T: SPTBaseObject {
let items: [T]
private struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let key = T.pluralKey, let codingKey = CodingKeys(stringValue: key) else {
fatalError()
}
items = try container.decode([T].self, forKey: codingKey)
}
}
If I understood correctly, you can use a function that takes an array of [T] where T should be subclass of SPTBaseObject and an overload that takes only one T object, like this:
func getPluralKey<T: SPTBaseObject>(_ array: [T]) {
print(T.pluralKey ?? "nil")
}
func getPluralKey<T: SPTBaseObject>(_ object: T) {
print(T.pluralKey ?? "nil")
}

Swift UserDefaults optional Int

I want to set or retrieve value from UserDefaults that is an optional Int, meaning it is either nil or set to a value. I am confused in the setter however. In the code below, I set it to either nil or an integer. I am not sure if this is correct. Is there a better or cleaner way (assuming if at all what I am doing is correct)?
public var selectedOption:myOption? {
get {
if let opt = UserDefaults.standard.object(forKey: "myKey") as? Int {
if let val = myOption(rawValue: opt) {
return val
}
}
return nil
}
set {
if let newVal = newValue {
UserDefaults.standard.set(newVal.rawValue, forKey:"myKey")
} else {
UserDefaults.standard.set(nil, forKey:"myKey")
}
}
}
You can reduce the setter
set {
UserDefaults.standard.set(newValue?.rawValue, forKey:"myKey")
}
Due to optional chaining nil is written out if newValue is nil.
public var selectedOption:myOption? {
get {
if let opt = UserDefaults.standard.object(forKey: "myKey") as? Int {
return myOption(rawValue: opt)
}
return nil
}
set {
UserDefaults.standard.set(newValue?.rawValue, forKey: "myKey")
}
}
Or
Confirm to codable protocol and store the enum instance in UserDefaults
enum myOption: Int, Codable {
case a
case b
}
public var selectedOption1:myOption? {
get {
if let storedObject: Data = UserDefaults.standard.object(forKey: "myKey") as? Data {
return try? PropertyListDecoder().decode(myOption.self, from: storedObject)
}
return nil
}
set {
UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: "myKey")
}
}

Can't save custom class array to UserDefaults

I'm trying to save a custom class array to UserDefaults but it doesn't work. I get nil back on if let. I looked everywhere online. I'm using Swift 4.2
extension UserDefaults {
func saveReciters(_ reciters: [Reciter]) {
do {
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: reciters, requiringSecureCoding: false)
self.set(encodedData, forKey: UD_RECITERS)
} catch {
debugPrint(error)
return
}
}
func getReciters() -> [Reciter] {
if let reciters = self.object(forKey: UD_RECITERS) as? Data {
return NSKeyedUnarchiver.unarchiveObject(with: reciters) as! [Reciter]
} else {
print("EMPTY RECITERS")
return [Reciter]()
}
}
}
UserInfo={NSDebugDescription=Caught exception during archival: -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600001babcc0
Thats my class:
class Reciter: NSCoding {
private(set) public var name: String
private(set) public var image: UIImage?
private(set) public var surahs: [Surah]
private(set) public var documentID: String
private let quranData = QuranData()
init(name: String, image: UIImage?, surahCount: Int?, documentID: String) {
self.name = name
self.image = image
self.documentID = documentID
if let surahCount = surahCount {
surahs = Array(quranData.getAllSurahs().prefix(surahCount))
} else {
surahs = quranData.getAllSurahs()
}
}
func encode(with aCoder: NSCoder) {
}
required init?(coder aDecoder: NSCoder) {
}
}
On my Surah class i get nil back. All other properties i get back succesfully
Most often I see developer's use codeable, here I am using user as an example:
YourDataModel.swift
struct User: Codable {
var userId: String = ""
var name: String = ""
var profileImageData: Data? }
UserDefaults.swift
import Foundation
extension UserDefaults {
/// The current user of the application, see `./Models/User.swift`
var currentUser: User? {
get {
guard let userData = self.object(forKey: #function) as? Data else { return nil }
return try? JSONDecoder().decode(User.self, from: userData)
}
set {
guard let newuser = newValue else { return }
if let userData = try? JSONEncoder().encode(newuser) {
self.set(userData, forKey: #function)
}
}
}
}
Transform the data into json data... #function is the function or value name i.e.
// For the case the user doesn't yet exist.
if ( UserDefaults.standard.currentUser == nil ) {
// Create a new user
user = User()
// Generate an id for the user, using a uuid.
user?.userId = UUID().uuidString
} else {
// otherwise, fetch the user from user defaults.
user = UserDefaults.standard.currentUser
}

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?
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 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.
Here is a modern Swift 5.1 #propertyWrapper, allowing to store any Codable object in form of a human readable JSON string:
#propertyWrapper struct UserDefaultEncoded<T: Codable> {
let key: String
let defaultValue: T
init(key: String, default: T) {
self.key = key
defaultValue = `default`
}
var wrappedValue: T {
get {
guard let jsonString = UserDefaults.standard.string(forKey: key) else {
return defaultValue
}
guard let jsonData = jsonString.data(using: .utf8) else {
return defaultValue
}
guard let value = try? JSONDecoder().decode(T.self, from: jsonData) else {
return defaultValue
}
return value
}
set {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
guard let jsonData = try? encoder.encode(newValue) else { return }
let jsonString = String(bytes: jsonData, encoding: .utf8)
UserDefaults.standard.set(jsonString, forKey: key)
}
}
}
Usage:
extension Song: Codable {}
#UserDefaultEncoded(key: "songs", default: [])
var songs: [Song]
func addSong(_ song: Song) {
// This will automatically store new `songs` value
// to UserDefaults
songs.append(song)
}
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.
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")
}
}
I'd imagine that it should be quite common to represent a user's settings as an observable object. So, here's an example of keeping observable data synchronised with user defaults and updated for xCode 11.4. This can be used in the context of environment objects also.
import SwiftUI
final class UserData: ObservableObject {
#Published var selectedAddress: String? {
willSet {
UserDefaults.standard.set(newValue, forKey: Keys.selectedAddressKey)
}
}
init() {
selectedAddress = UserDefaults.standard.string(forKey: Keys.selectedAddressKey)
}
private struct Keys {
static let selectedAddressKey = "SelectedAddress"
}
}
Swift 5
If you want need to save struct in UserDefault using only on data format.
Smaple struct
struct StudentData:Codable{
var id: Int?
var name: String?
var createdDate: String?
// for decode the value
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: codingKeys.self)
id = try? values?.decodeIfPresent(Int.self, forKey: .id)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
createdDate = try? values?.decodeIfPresent(String.self, forKey: .createdDate)
}
// for encode the value
func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: codingKeys.self)
try? values.encodeIfPresent(id, forKey: .id)
try? values.encodeIfPresent(name, forKey: .name)
try? values.encodeIfPresent(createdDate, forKey: .createdDate)
}
}
There are two types to convert as data
Codable (Encodable and Decodable).
PropertyListEncoder and PropertyListDecoder
First we using the Codable (Encodable and Decodable) to save the struct
Example for save value
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z")
guard let data = try? JSONEncoder().encode(value) else {
fatalError("unable encode as data")
}
UserDefaults.standard.set(data, forKey: "Top_student_record")
Retrieve value
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else {
// write your code as per your requirement
return
}
guard let value = try? JSONDecoder().decode(StudentData.self, from: data) else {
fatalError("unable to decode this data")
}
print(value)
Now we using the PropertyListEncoder and PropertyListDecoder to save the struct
Example for save value
let value = StudentData(id: 1, name: "Abishek", createdDate: "2020-02-11T11:23:02.3332Z")
guard let data = try? PropertyListEncoder().encode(value) else {
fatalError("unable encode as data")
}
UserDefaults.standard.set(data, forKey: "Top_student_record")
Retrieve value
guard let data = UserDefaults.standard.data(forKey: "Top_student_record") else {
// write your code as per your requirement
return
}
guard let value = try? PropertyListDecoder().decode(StudentData.self, from: data) else {
fatalError("unable to decode this data")
}
print(value)
In your convenience you can use the any type to save the struct in userDefault.
Here is a simpler solution
#propertyWrapper
struct CodableUserDefault<Value: Codable> {
let key: String
let defaultValue: Value
private let container: UserDefaults = .standard
var wrappedValue: Value {
get {
guard let data = container.data(forKey: key), let object = try? JSONDecoder().decode(Value.self, from: data) else {
return defaultValue
}
return object
}
set {
container.set(try? JSONEncoder().encode(newValue), forKey: key)
}
}
}
Usage
enum ACodableEnum: String, Codable {
case first
case second
}
class SomeController {
#CodableUserDefault<ACodableEnum>(key: "key", defaultValue: .first)
private var aCodableEnum: ACodableEnum
}

Resources