Search code examples
swiftnsuserdefaultsuserdefaults

Save Dictionary of custom objects in Swift using UserDefaults


I'm writing an app to work with contacts and trying to save information with UserDefaults but I'm getting SIGABRT. Here's my code, what I'm doing wrong?

class Contact{
var name = String()
var phone = String()

init(name: String, phone: String){
    self.name=name
    self.phone=phone
}
required init(coder decoder: NSCoder){
    self.name = (decoder.decodeObject(forKey: "name") as? String)!
    self.phone = (decoder.decodeObject(forKey: "phone") as? String)!
}
func encode(with coder: NSCoder){
    coder.encode(name, forKey: "name")
    coder.encode(phone, forKey: "phone")
}
}

Creating contact in ViewDidLoad just for test:

        let userDefaults = UserDefaults.standard
var contactDictionary = Dictionary<String, [Contact]>()

  override func viewDidLoad() {
    super.viewDidLoad()
    contactDictionary["A"] = [Contact(name: "Annabel",phone: "000")]

    let encodedData = NSKeyedArchiver.archivedData(withRootObject: contactDictionary)
    userDefaults.set(encodedData, forKey: "contactDictionary")
    userDefaults.synchronize()
    if let data = userDefaults.data(forKey: "contactDictionary"){
        print("yep")
        contactDictionary = (NSKeyedUnarchiver.unarchiveObject(with: data) as? [String : [Contact]])!
    }
    else{
        print("nope")
    }
}

Solution

  • You need to make your class NSCoding compliant and inherit from NSObject. Change your Contact declaration to class Contact: NSObject, NSCoding {. And Btw conditionally cast to force unwrap it later is pointless. decoder.decodeObject(forKey: "name") as! String

    class Contact: NSObject, NSCoding {
        var name = String()
        var phone = String()
        init(name: String, phone: String){
            self.name=name
            self.phone=phone
        }
        required init(coder decoder: NSCoder){
            self.name = decoder.decodeObject(forKey: "name") as! String
            self.phone = decoder.decodeObject(forKey: "phone") as! String
        }
        func encode(with coder: NSCoder){
            coder.encode(name, forKey: "name")
            coder.encode(phone, forKey: "phone")
        }
    }
    

    Testing:

    let contactDictionary = ["A":[Contact(name: "Annabel",phone: "000")]]
    
    let encodedData = NSKeyedArchiver.archivedData(withRootObject: contactDictionary)
    UserDefaults.standard.set(encodedData, forKey: "contactDictionary")
    
    if let data = UserDefaults.standard.data(forKey: "contactDictionary") {
        print("yep")
        let contactDictionary2 = NSKeyedUnarchiver.unarchiveObject(with: data) as! [String : [Contact]]
    }
    else{
        print("nope")
    }