We have a custom object setup like so:
struct BallPark: Codable,Equatable {
static func == (lhs: LeanVenue, rhs: LeanVenue) -> Bool {
return lhs.id == rhs.id
}
let id: String
let name: String?
let location: VenueCoordinates?
let mapEnabled: Bool?
let facilityStatus: String?
}
struct VenueCoordinates: Codable {
let latitude: String?
let longitude: String?
var lat: Double? {
guard let latitud = latitude else { return nil}
return Double(latitud)
}
var lon: Double? {
guard let longitude = longitude else { return nil}
return Double(longitude)
}
}
We are trying to convert it to type Data so it can be saved to user defaults like so:
guard let bestBallParkToSave = theBestBallPark // this is of type BallPark, after fetching data
else {return}
let encodedBestBallPark = NSKeyedArchiver.archivedData(withRootObject: bestBallParkToSave)
UserDefaults.standard.set(encodedBestBallPark, forKey: "favoriteBallPark")
The problem is it causes an error when trying to convert:
'NSInvalidArgumentException', reason: '-[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x6000027dca80'
I've tried using this stack overflow answer: How to convert custom object to Data Swift but there wasn't really a clear answer except making sure it conforms to Codable which I believe my struct and everything in it does. If you have any suggestions please let me know.
The issue there. is that you are mixing up Codable with NSCoding. To use NSKeyedArchiver's archivedData method you need to have a class that conforms to NSCoding. In you case you have a structure that conforms to Codable so you need to use JSONEncoder encode method. Note: Your equatable method declaration was wrong:
struct BallPark: Codable, Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
let id: String
let name: String?
let location: VenueCoordinates?
let mapEnabled: Bool?
let facilityStatus: String?
}
struct VenueCoordinates: Codable {
let latitude: String?
let longitude: String?
var lat: Double? {
guard let latitud = latitude else { return nil}
return Double(latitud)
}
var lon: Double? {
guard let longitude = longitude else { return nil}
return Double(longitude)
}
}
let bestBallParkToSave = BallPark.init(id: "1", name: "Steve", location: .init(latitude: "20.0", longitude: "40.0"), mapEnabled: true, facilityStatus: "a status")
do {
let encodedBestBallPark = try JSONEncoder().encode(bestBallParkToSave)
UserDefaults.standard.set(encodedBestBallPark, forKey: "favoriteBallPark")
if let ballParkData = UserDefaults.standard.data(forKey: "favoriteBallPark") {
let loadedBallPark = try JSONDecoder().decode(BallPark.self, from: ballParkData)
print(loadedBallPark) // BallPark(id: "1", name: Optional("Steve"), location: Optional(VenueCoordinates(latitude: Optional("20.0"), longitude: Optional("40.0"))), mapEnabled: Optional(true), facilityStatus: Optional("a status"))
}
} catch {
print(error)
}
You can also make your life easier extending UserDefaults and create customs encoding and decoding methods:
extension UserDefaults {
func decodeObject<T: Decodable>(forKey defaultName: String, using decoder: JSONDecoder = .init()) throws -> T {
try decoder.decode(T.self, from: data(forKey: defaultName) ?? .init())
}
func encode<T: Encodable>(_ value: T, forKey defaultName: String, using encoder: JSONEncoder = .init()) throws {
try set(encoder.encode(value), forKey: defaultName)
}
}
Usage:
let bestBallParkToSave = BallPark(id: "1", name: "Steve", location: .init(latitude: "20.0", longitude: "40.0"), mapEnabled: true, facilityStatus: "a status")
do {
try UserDefaults.standard.encode(bestBallParkToSave, forKey: "favoriteBallPark")
let loadedBallPark: BallPark = try UserDefaults.standard.decodeObject(forKey: "favoriteBallPark")
print(loadedBallPark) // BallPark(id: "1", name: Optional("Steve"), location: Optional(VenueCoordinates(latitude: Optional("20.0"), longitude: Optional("40.0"))), mapEnabled: Optional(true), facilityStatus: Optional("a status"))
} catch {
print(error)
}