Your issue there is that you are using the wrong method to decode your integers and your boolean. I would also implement the required decoder instead of a convenience initializer.

required init(coder decoder: NSCoder) {
    self.idNum = decoder.decodeInteger(forKey: "idNum")
    self.topicId = decoder.decodeInteger(forKey: "topicId")
    self.tryCount = decoder.decodeInteger(forKey: "tryCount")
    self.score = decoder.decodeInteger(forKey: "score")
    self.isOpen = decoder.decodeBool(forKey: "isOpen")
    self.lastPlayedDate = decoder.decodeObject(forKey: "lastPlayedDate") as? Date
}

Note that you can also use Swift 4 or later Codable protocol to encode and decode your custom classes/structures and save them as data inside UserDefaults or as a plist file inside your application support folder. Last but not least don’t use setValue and value(forKey:) to save and retrieve your data, user defaults has a specific method for retrieving data called data(forKey:) and set(_ value: Any) to persist your data:

extension UserDefaults {
    func decode<T: Decodable>(_ type: T.Type, forKey defaultName: String) throws -> T {
        try JSONDecoder().decode(T.self, from: data(forKey: defaultName) ?? .init())
    }
    func encode<T: Encodable>(_ value: T, forKey defaultName: String) throws {
        try set(JSONEncoder().encode(value), forKey: defaultName)
    }
}

class DbInsideLevel: Codable {
    let idNum: Int!
    var topicId: Int = 0
    var tryCount: Int = 0
    var score: Int = 0
    var isOpen: Bool = false
    var lastPlayedDate: Date?
    init(idNum: Int, topicId: Int, tryCount: Int = 0, score: Int = 0, open: Bool, lastPlayedDate: Date?) {
        self.idNum = idNum
        self.topicId = topicId
        self.tryCount = tryCount
        self.score = score
        self.isOpen = open
        self.lastPlayedDate = lastPlayedDate
    }
    func update(score: Int) {
        if score > self.score {
            self.score = score
        }
        self.tryCount = self.tryCount + 1
    }
}

Playground testing

let insideLevel = DbInsideLevel(idNum: 1, topicId: 2, tryCount: 3, score: 4, open: true, lastPlayedDate: Date())
do {
    try UserDefaults.standard.encode(insideLevel, forKey: "insideLevel")
    let decodedLevel = try UserDefaults.standard.decode(DbInsideLevel.self, forKey: "insideLevel")
    print("decodedLevel idNum", decodedLevel.idNum) // decodedLevel idNum Optional(1)
} catch {
    print(error)
}

edit/update:

It does work for arrays of Codable types as well:

let insideLevel = DbInsideLevel(idNum: 1, topicId: 2, tryCount: 3, score: 4, open: true, lastPlayedDate: Date())
do {
    try UserDefaults.standard.encode([insideLevel], forKey: "insideLevels")
    let decodedLevel = try UserDefaults.standard.decode([DbInsideLevel].self, forKey: "insideLevels")
    print("decodedLevel idNum", decodedLevel.first?.idNum) // decodedLevel idNum Optional(1)
} catch {
    print(error)
}

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top