2

This is pretty simple but can't seem to find the correct information to solve saving an array like this in User Defaults.

It says it's not a property that NSUser Defaults Excepts.

Code:

    var notificationList: [(type: String,imageName: String, text: String, date: String, seen: Bool)] = [(type: "Default",imageName: "ClearPartioned", text: "", date: "", seen: true)]


    if (UserDefaults.standard.object(forKey: "notificationList")) == nil { // first time launching

        print("making notification list")

        UserDefaults.standard.set(notificationList, forKey: "notificationList")
        UserDefaults.standard.synchronize()

        print("\(notificationList)")

    } else {

        print("getting saved array")

        notificationList = (UserDefaults.standard.object(forKey: "notificationList") as! [(type: String, imageName: String, text: String, date: String, seen: Bool)])

        print("\(notificationList)")
    }

enter image description here

Update:

This is closer but gives error found in this question here. These are the closet answers I have been able to find and there either out dated or crash the system.

Code:

    if (UserDefaults.standard.object(forKey: "notificationList")) == nil { // first time launching

        print("making notification list")

        let encodedData = NSKeyedArchiver.archivedData(withRootObject: notificationList)
        UserDefaults.standard.set(encodedData, forKey: "notificationList")
        UserDefaults.standard.synchronize()

    } else {

        print("getting saved array")

        notificationList = (UserDefaults.standard.object(forKey: "notificationList") as! [(type: String, imageName: String, text: String, date: String, seen: Bool)])

        print("\(notificationList)")
    }

Update 2: This is best answer implementation From Dhiru

Code:

  if (UserDefaults.standard.object(forKey: "notificationList")) == nil { // first time launching
        print("making notification list")

        let notificationData = NSKeyedArchiver.archivedData(withRootObject: notificationList)
        UserDefaults.standard.set(notificationData, forKey: "notificationList")
        UserDefaults.standard.synchronize()

    } else {

        print("getting saved array")

        let decodedData  = UserDefaults.standard.object(forKey: "notificationList") as! Data
        let notificationList = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as AnyObject

        print("\(notificationList)")
    }

Its giving me an error that crashes system

   *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x1c011f380'
   libc++abi.dylib: terminating with uncaught exception of type NSException

Im sure this code would fix it but this is horribly implemented with multiple errors below because I have no clue how to use this code.

Code:

   func (coder aDecoder: NSCoder) {
        if let notificationList = aDecoder.decodeObjectForKey("notificationList") {
            self.notificationList = notificationList
        }
    }

    func encodeWithCoder(aCoder: NSCoder) {
        if let notificationList = notificationList {
            aCoder.encodeObject(notificationList, forKey: "notificationList")
        }
    }
Matusalem Marques
  • 2,399
  • 2
  • 18
  • 28
Hunter
  • 1,321
  • 4
  • 18
  • 34
  • Not sure about tuples, but custom object have to confront the `NSCoding` protocol to encode the params into data, then only can be saved in `UserDefault` – Tj3n Jul 06 '17 at 04:53
  • 2
    Try [searching on the error](https://stackoverflow.com/search?q=%5Bswift%5D+attempt+to+insert+non-property+list+object). This has been covered many times before. – rmaddy Jul 06 '17 at 04:54
  • Save it as Data using `NSKeyedArchiver` – Abhi V Jul 06 '17 at 05:00

2 Answers2

3

You have to store your Object in form of Data Convert into data using NSKeyedArchiver.archivedData(withRootObject:)

Convert back to Object using NSKeyedUnarchiver.unarchiveObject(with:)

Saving Data for UserDefaults

let notificationData = NSKeyedArchiver.archivedData(withRootObject: notificationList)
UserDefaults.standard.set(notificationData, forKey: "notificationList")

Retrive Data from User UserDefaults

let decodedData  = UserDefaults.standard.object(forKey: "notificationList") as! Data
let notificationList = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! AnyObject
Dhiru
  • 3,040
  • 3
  • 25
  • 69
2

This is how I actually save a Custom Object created in the app in Swift 4.

First, we create 3 protocols for our purpose of saving the custom object in UserDefaults. The logic behind is to convert the Custom Object into a normalized Dictionary/Array form.

This can be applied to any kind of Object which you have created.

The 3 protocols are:

  1. Decoder (Used to decode the dictionary into custom object)
  2. Encoder (Used to encode the custom object into dictionary)
  3. UserDefaultsProtocol (Used to save, delete, update & retrieve the custom object from UserDefault)

Decoder Protocol

protocol Decoder {
    associatedtype T
    static func decode(dictionary: [String: Any]) -> T
}

Encoder Protocol

protocol Encoder {
    func encode() -> [String: Any]
}

UserDefaultsProtocol

protocol UserDefaultsDelegate: class {
    associatedtype T
    func saveToUserDefaults()
    static func removeFromUserDefaults()
    static func retrieveFromUserDefaults() -> T?
}

As per your question, NotificationList Object would look like this

class NotificationList {
    var type: String = ""
    var imageName: String = ""
    var text: String = ""
    var date: String = ""
    var seen: Bool = false
}

Now, you need to confirm all the 3 mentioned protocols to NotificationList. (Swift Best Practice: Use of Extensions & Protocols)

class NotificationList {
    private struct Constants {
        static let RootKey = "notification_list"
        static let TypeKey = "type"
        static let ImageNameKey = "image_name"
        static let TextKey = "text"
        static let DateKey = "date"
        static let SeenKey = "seen"
    }

    var type: String = ""
    var imageName: String = ""
    var text: String = ""
    var date: String = ""
    var seen: Bool = false

    typealias T = NotificationList
}

extension NotificationList: Encoder {
    func encode() -> [String : Any] {
        return [
            Constants.TypeKey: type,
            Constants.ImageNameKey: imageName,
            Constants.TextKey: text,
            Constants.DateKey: date,
            Constants.SeenKey: seen
        ]
    }
}

extension NotificationList: Decoder {
    static func decode(dictionary: [String: Any]) -> NotificationList {
        let type = dictionary[Constants.TypeKey] as! String
        let imageName = dictionary[Constants.ImageNameKey] as! String
        let text = dictionary[Constants.TextKey] as! String
        let date = dictionary[Constants.DateKey] as! String
        let seen = dictionary[Constants.SeenKey] as! Bool

        let notificationList = NotificationList()
        notificationList.type = type
        notificationList.imageName = imageName
        notificationList.text = text
        notificationList.date = date
        notificationList.seen = seen
        return notificationList
    }
}

extension NotificationList: UserDefaultsDelegate {

    func saveToUserDefaults() {
        UserDefaults.standard.setValue(encode(), forKey: Constants.RootKey)
    }

    static func retrieveFromUserDefaults() -> NotificationList? {
        guard let encodedNotificationList = UserDefaults.standard.dictionary(forKey: Constants.RootKey) else {
            return nil
        }
        return NotificationList.decode(dictionary: encodedNotificationList)
    }

    static func removeFromUserDefaults() {
        UserDefaults.standard.removeObject(forKey: Constants.RootKey)
    }
}

How to save NotificationList to UserDefaults?

var notificationList = NotificationList()
notificationList.type = "Default"
notificationList.imageName = "ClearPartioned"
notificationList.text = ""
notificationList.date = ""
notificationList.seen = true

Save to UserDefaults

notificationList.saveToUserDefaults()

Retrieve from UserDefaults

if let notificationList = NotificationList.retrieveFromUserDefaults() {
      // You will get the instance of notification list saved in UserDefaults
}

HOW TO SAVE ARRAY OF NOTIFICATION LIST?

Say notificationLists contains the array of notificationList objects.

var notificationListsArray = [[String: Any]]()

notificationLists.forEach {
     notificationListsArray.append($0.encode())
}

Save that array of dictionary to UserDefaults

UserDefaults.standard.setValue(notificationListsArray, forValue: "notificationLists")
aashish tamsya
  • 4,903
  • 3
  • 23
  • 34
  • Wow quite a set up! It's the best and really only answer so far so I'll mark it. – Hunter Jul 06 '17 at 05:47
  • I hope it helped to solve your problem. As It requires some setup upfront. But after that, you can use it in anywhere in your application, which is the real power of Swift. – aashish tamsya Jul 06 '17 at 05:50
  • 1
    Yep Thank You this is a incredibly well written answer – Hunter Jul 06 '17 at 05:57