arraysswiftnscoding

Save Array of dictionaries with Enum type, NSCoding


I have been trying to figure out how to save an arrays of dictionaries using Enum types with NSCoding.

I have an enum with Medal types

enum Medal: String {
   case Unearned = "NoMedal"
   case Gold = "GoldMedal"
   case Silver = "SilverMedal"
}

I have an array of dictionaries with the medal types in my GameData.swift class.

var medals: [[String: Medal]] = [
    ["1": .Unearned, "2": .Unearned, "3": .Unearned]
    ...
]

Than I have this code in the decoder method

convenience required init?(coder decoder: NSCoder) {
    self.init()

    medals = decoder.decodeObjectForKey(Key.medals) as? [[String: Medal]] ?? medals
}

and this is the encoder method which I believe is causing me the issues

// MARK: - Encode
func encodeWithCoder(encoder: NSCoder) {

    encoder.encodeObject(medals as? AnyObject, forKey: Key.medals)
}

The problem is on a restart it is not saving/loading the medal array, it keeps resetting to default.

I have also tried this

 encoder.encodeObject(medals as? [[String: Medal]], forKey: Key.medals)

and it causes a compiler error.

I also cannot use the regular syntax

 encoder.encodeObject(medals, forKey: Key.medals)

Like I would for a array of dictionaries using normal values (e.g [String: Int]) as the compiler will also throw an error


Solution

  • medals is not an AnyObject, so when you encode it as? AnyObject you get nil.

    Just because the enum has raw string values doesn't mean you can automatically bridge it to [String:String]. You have to do that yourself. For example (using Airspeed Velocity's version of mapValues):

    convenience required init?(coder decoder: NSCoder) {
        self.init()
    
        if let medalStrings = decoder.decodeObjectForKey(Key.medals) as? [[String: String]] {
            medals = medalStrings.map { $0.mapValues { Medal(rawValue: $0) ?? .Unearned } }
        }
    
    }
    
    func encodeWithCoder(encoder: NSCoder) {
        let medalStrings = medals.map { $0.mapValues { $0.rawValue } }
        encoder.encodeObject(medalStrings, forKey: Key.medals)
    }