Search code examples
arraysswiftnsuserdefaultsdata-persistence

How Do I Save an Array of Objects to NSUserDefaults?


I was wondering how to save an array of objects to a NSUserDefault. I've read about encoding the data array, but was unsure about how the process works.

This is my array:

//EventData class with EventDataArray
class EventData {
    
    static var EventDataArray: [EventModel] = []
    
}

And this is my data model:

struct EventModel {
    var id = UUID()
    var eventName: String
    var fromTime: Date
    var toTime: Date
    var fromTimeString: String
    var toTimeString: String
    var color: UIColor
}

EDIT: UPDATED ARRAY CLASS

class EventData: Codable {
    
    static var EventDataArray: [EventModel] = []
    
}

I would appreciate any advice on the topic!


Solution

  • To save a structure in UserDefaults you need to first encode it to be able to save it as Data. So you need to make your custom structure conform to Codable:

    struct Event: Codable {
        let id: UUID
        let name: String
        let start, end: Date
        let fromTime, toTime: String
        let color: Color
        init(id: UUID = .init(),
             name: String,
             start: Date,
             end: Date,
             fromTime: String,
             toTime: String,
             color: Color) {
            self.id = id
            self.name = name
            self.start = start
            self.end = end
            self.fromTime = fromTime
            self.toTime = toTime
            self.color = color
        }
    }
    

    Note that you can not conform UIColor to Codable but you can create a custom Color structure:

    struct Color: Codable {
        let (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat)
    }
    

    extension Color {
        init?(_ uiColor: UIColor) {
            var (r, g, b, a): (CGFloat,CGFloat,CGFloat,CGFloat) = (0, 0, 0, 0)
            guard uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) else { return nil }
            self.init(r: r, g: g, b: b, a: a)
        }
        var color: UIColor { .init(red: r, green: g, blue: b, alpha: a) }
    }
    

    extension UIColor {
        convenience init(_ color: Color) {
            self.init(red: color.r, green: color.g, blue: color.b, alpha: color.a)
        }
        var color: Color? { Color(self) }
    }
    

    Regarding your class you can also make it conform to Codable or inherit from NSObject and conform to NSCoding:

    class Events: NSObject, NSCoding {
        private override init() { }
        static var shared = Events()
        var events: [Event] = []
        required init(coder decoder: NSCoder) {
            events = try! JSONDecoder().decode([Event].self, from: decoder.decodeData()!)
        }
        func encode(with coder: NSCoder) {
            try! coder.encode(JSONEncoder().encode(events))
        }
    }
    

    Playground testing:

    Events.shared.events = [.init(name: "a",
                                  start: Date(),
                                  end: Date(),
                                  fromTime: "fromTime",
                                  toTime: "toTime",
                                  color: .init(r: 0, g: 0, b: 1, a: 1)),
                            .init(name: "b",
                                  start: Date(),
                                  end: Date(),
                                  fromTime: "fromTimeB",
                                  toTime: "toTimeB",
                                  color: .init(r: 0, g: 1, b: 0, a: 1))]
    
    print(Events.shared.events)
    let data = try! NSKeyedArchiver.archivedData(withRootObject: Events.shared, requiringSecureCoding: false)
    UserDefaults.standard.set(data, forKey: "events")
    Events.shared.events = []
    print(Events.shared.events)
    let loadedData = UserDefaults.standard.data(forKey: "events")!
    Events.shared = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(loadedData) as! Events
    print(Events.shared.events)
    

    This will print

    [Event(id: C7D9475B-773E-4272-84CC-56CAEAA73D0C, name: "a", start: 2021-01-26 05:17:30 +0000, end: 2021-01-26 05:17:30 +0000, fromTime: "fromTime", toTime: "toTime", color: Color(r: 0.0, g: 0.0, b: 1.0, a: 1.0)), Event(id: 0BEA4225-2F63-4EEB-AF10-F3EF4C84D050, name: "b", start: 2021-01-26 05:17:30 +0000, end: 2021-01-26 05:17:30 +0000, fromTime: "fromTimeB", toTime: "toTimeB", color: Color(r: 0.0, g: 1.0, b: 0.0, a: 1.0))]
    []
    [Event(id: C7D9475B-773E-4272-84CC-56CAEAA73D0C, name: "a", start: 2021-01-26 05:17:30 +0000, end: 2021-01-26 05:17:30 +0000, fromTime: "fromTime", toTime: "toTime", color: Color(r: 0.0, g: 0.0, b: 1.0, a: 1.0)), Event(id: 0BEA4225-2F63-4EEB-AF10-F3EF4C84D050, name: "b", start: 2021-01-26 05:17:30 +0000, end: 2021-01-26 05:17:30 +0000, fromTime: "fromTimeB", toTime: "toTimeB", color: Color(r: 0.0, g: 1.0, b: 0.0, a: 1.0))]