1

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!

2 Answers 2

3

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))]

4
  • Ok, but how do I make the array inside EventData conform to Codable? Is this possible now that the structures EventModel and Color are now supported by Codable? Commented Jan 27, 2021 at 8:35
  • The array already conforms to Codable. If Event conforms to Codable the array conforms as well as you can see I am encoding and decoding [Event].self
    – Leo Dabus
    Commented Jan 27, 2021 at 13:12
  • Ok, I made the class object conform to codable, would the save and read methods be the same? This is a different question I made with a bit more context: stackoverflow.com/questions/65916487/… Commented Jan 27, 2021 at 13:44
  • 1
    Yes, with a bit of tweaking your solution worked. Thank you for the help! Commented Jan 27, 2021 at 13:57
0

Simply use: UserDefaults().set(try? PropertyListEncoder().encode(EventDataArray), forKey: "eventDataPersisted") (of course, feel free to name the key whatever you want).

Also, as a rule of thumb, variable names typically follow camelCase.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.