Search code examples
iosswiftuserdefaults

Retrieving custom Object from NSUserDefaults


I have a dictionary of values

class Objects {
    let values = [
    "AAA": ["AAAAAAA", "111111111"],
    "BBB": ["BBBBBBBB", "2222222"],
    "CCC": ["CCCCCCCC", "3333333333"],
    "DDD": ["DDDDDD", "44444444"],
    ]
}

Which I turn into custom objects and display in a tableview.

struct  Object {
   var heading : String!
   var imageName: String!
}

Then the user can select two objects to store in UserDefaults

let defaults = UserDefaults.standard

func addObject(_ object1: String, object2: String) {

    // Get objects for user
    var userObjects = fetchObjectsFromUserDefaults()

    // Add to user currencies
    userObjects.append([object1,object2])

    //Update user defaults value for key
    // [ [Object1, Object2], [Object1, Object2] ]

    defaults.set(userObject, forKey: "userCurrencies")
}

// Gets [[String]] values from user defaults for key
func fetchObjectsFromUserDefaults() -> [[String]] {
    if let objects = UserDefaults.standard.value(forKey: "userObjects") {
        return objects as! [[String]]
    } else {
        return []
    }
}


// Uses [[String]] values and turns them into objects by using the dictionary to determine property values 

func getObject() -> [[Object]] {
    let userObject = fetchObjectsFromUserDefaults()
    // [ [Object1, Object2], [Object1, Object2] ]

    let object = Object()

    var fetchedObject = [[Object]]()

    if !userObjects.isEmpty {
        for c in userObjects {
            var set = [Object]()
            if let val = object.available[c[0]] {
                set.append(Currency(currencyTitle: c[0], imageName: val[0] ))
            }

            if let val2 = object.available[c[1]] {
                set.append(Currency(currencyTitle: c[0], imageName: val2[0] ))
            }

            if !set.isEmpty {
                fetchedObjects.append(set)
            }
        }
        return fetchedObjects
    }

    return [[]]

}

View Controller

Here I get the objects to load into the TableView

let fetched = dataManager.getObjects
print(fetched)
self.objects = fetched()

However this prints out

(Function)

What am I doing wrong and is their a better method of storing and retrieving this data from user defaults ? I feel this is over kill and there is a swifter and safer approach.


Solution

  • Step 1.

    Make your struct Codable. The compiler will write all of the functions for you if all of the members of your struct are Codable and fortunately String is Codable so its just:

    struct Object: Codable {
      var heading : String!
      var imageName: String!
    }
    

    Step 2.

    The problem with Codable is that it converts to and from Data, but you want to convert to and from a Dictionary. Fortunately JSONSerialization converts from Data to Dictionary so make a new protocol and give it a default implementation with a protocol extension:

    protocol JSONRepresentable {
      init?(json: [String: Any])
      func json() -> [String: Any]
    }
    extension JSONRepresentable where Self: Codable {
      init?(json: [String:Any]) {
        guard let value = (try? JSONSerialization.data(withJSONObject: json, options: []))
          .flatMap ({ try? JSONDecoder().decode(Self.self, from: $0) }) else {
            return nil
        }
        self = value
      }
      func json() -> [String:Any] {
        return (try? JSONEncoder().encode(self))
          .flatMap { try? JSONSerialization.jsonObject(with: $0, options: []) } as? [String: Any] ?? [:]
      }
    }
    

    Step 3.

    Conform your struct to JSONRepresentable

    struct Object: Codable, JSONRepresentable {
      var heading : String!
      var imageName: String!
    }
    

    Step 4.

    Place your object into Userdefaults and get it out again:

    let o = Object.init(heading: "s", imageName: "a").json()
    UserDefaults.standard.set(o, forKey: "test")
    print(Object.init(json: UserDefaults.standard.dictionary(forKey: "test") ?? [:]))
    

    Here is the whole playground if you want to try:

    import UIKit
    
    struct Object: Codable, JSONRepresentable {
      var heading : String!
      var imageName: String!
    }
    
    protocol JSONRepresentable {
      init?(json: [String: Any])
      func json() -> [String: Any]
    }
    extension JSONRepresentable where Self: Codable {
      init?(json: [String:Any]) {
        guard let value = (try? JSONSerialization.data(withJSONObject: json, options: []))
          .flatMap ({ try? JSONDecoder().decode(Self.self, from: $0) }) else {
            return nil
        }
        self = value
      }
      func json() -> [String:Any] {
        return (try? JSONEncoder().encode(self))
          .flatMap { try? JSONSerialization.jsonObject(with: $0, options: []) } as? [String: Any] ?? [:]
      }
    }
    
    let o = Object.init(heading: "s", imageName: "a").json()
    UserDefaults.standard.set(o, forKey: "test")
    print(Object.init(json: UserDefaults.standard.dictionary(forKey: "test") ?? [:]))