I would like to be able to save a Custom-struct
to UserDefaults
but for that I need it to be Codable
.. I tried it like this:
struct Wishlist: Codable {
var name: String
var image: UIImage
var wishData: [Wish]
var color: UIColor
var textColor: UIColor
var index: Int
}
But that gives me this error
:
Type 'Wishlist' does not conform to protocol 'Decodable'
Here is my Class Wish
, maybe that's where the problem is:
class Wish: NSObject {
public var wishName : String?
public var checkedStatus : Bool?
public var wishLink : String?
public var wishPrice : String?
public var wishNote : String?
public var wishImage : UIImage?
init(withWishName name: String, link: String, price: String, note: String, image: UIImage, checked: Bool) {
super.init()
wishName = name
checkedStatus = checked
wishLink = link
wishPrice = price
wishNote = note
wishImage = image
}
}
What am I doing wrong here??
You will need to make Wish
adopt Codable
.
But because UIImage
and UIColor
are not Codable
, you’ll have to manually implement them as outlined in Encoding and Decoding Custom Types:
struct Wishlist: Codable {
var name: String
var image: UIImage
var wishes: [Wish]
var color: UIColor
var textColor: UIColor
var index: Int
init(name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int) {
self.name = name
self.image = image
self.wishes = wishes
self.color = color
self.textColor = textColor
self.index = index
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
wishes = try values.decode([Wish].self, forKey: .wishData)
color = try values.decode(Color.self, forKey: .color).uiColor
textColor = try values.decode(Color.self, forKey: .textColor).uiColor
index = try values.decode(Int.self, forKey: .index)
let data = try values.decode(Data.self, forKey: .image)
guard let image = UIImage(data: data) else {
throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
}
self.image = image
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(wishes, forKey: .wishData)
try container.encode(Color(uiColor: color), forKey: .color)
try container.encode(Color(uiColor: textColor), forKey: .textColor)
try container.encode(index, forKey: .index)
try container.encode(image.pngData(), forKey: .image)
}
}
struct Wish: Codable {
public var name: String
public var checkedStatus: Bool
public var link: String
public var price: String
public var note: String
public var image: UIImage
init(name: String, link: String, price: String, note: String, image: UIImage, checkedStatus: Bool) {
self.name = name
self.checkedStatus = checkedStatus
self.link = link
self.price = price
self.note = note
self.image = image
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
checkedStatus = try values.decode(Bool.self, forKey: .checkedStatus)
link = try values.decode(String.self, forKey: .link)
price = try values.decode(String.self, forKey: .price)
note = try values.decode(String.self, forKey: .note)
let data = try values.decode(Data.self, forKey: .image)
guard let image = UIImage(data: data) else {
throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
}
self.image = image
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(checkedStatus, forKey: .checkedStatus)
try container.encode(link, forKey: .link)
try container.encode(price, forKey: .price)
try container.encode(note, forKey: .note)
try container.encode(image.pngData(), forKey: .image)
}
}
Where I’d use this as a convenient way to encode UIColor
objects:
struct Color: Codable {
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat
init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
}
init(uiColor: UIColor) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
}
var uiColor: UIColor { UIColor(red: red, green: green, blue: blue, alpha: alpha) }
}
Note, I did a couple of unrelated changes:
I made both of these struct
. I wouldn’t introduce reference types (much less NSObject
subclasses) unless necessary.
I simplified some of the property names. E.g. in Wish
, we wouldn’t generally use wish
prefix in property names. I also wouldn’t use “data” in a property name unless it was, in fact, a Data
.
I updated init
methods to use standard naming conventions.
By the way, another approach is to avoid using UIKit types within model types at all. This eliminates the need for custom encoders/decoders at all. And you can have platform-specific extensions, that provide the necessary convenience initializers that bridge to the UIKit types. E.g.:
// MARK: - Wishlist
struct Wishlist: Codable {
let name: String
let imageData: Data // rather than `UIImage`
let wishes: [Wish]
let color: Color // rather than `UIColor`
let textColor: Color // rather than `UIColor`
let index: Int
}
// MARK: Wishlist UIKit extension
#if os(iOS)
extension Wishlist {
init(name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int) {
self.init(
name: name,
imageData: image.pngData()!,
wishes: wishes,
color: Color(uiColor: color),
textColor: Color(uiColor: textColor),
index: index
)
}
var image: UIImage? { UIImage(data: imageData) }
}
#endif
// MARK: - Wish
struct Wish: Codable {
let name: String
let checkedStatus: Bool
let link: URL // rather than `String`
let price: String
let note: String
let imageData: Data // rather than `UIImage`
}
// MARK: Wish UIKit extension
#if os(iOS)
extension Wish {
init(name: String, link: URL, price: String, note: String, image: UIImage, checkedStatus: Bool) {
self.init(
name: name,
checkedStatus: checkedStatus,
link: link,
price: price,
note: note,
imageData: image.pngData()!
)
}
var image: UIImage? { UIImage(data: imageData) }
}
#endif
Note, I not only eliminated UIColor
from the model types, but also UIImage
, too. Now, above I shifted the UIImage
to Data
, but really, the image payload probably does not belong in this model type at all. You should just have image URLs or asset identifiers, and decouple the image fetch and storage from the model altogether. (Because images and model objects tend to have very different memory characteristics, you often want images fetched as they are needed and stored within some flushable cache. But that is beyond the scope of this question.)
in your case, you should add CodingKeys
enum and not using UIColor
nor UIImage
data type. I got the same error before, but then I realized that the CodingKey doesn't match with the struct nor there is non-codable
Data type. just change the data type to your custom codable object.
wrong example:
public struct DtClip: Codable {
// MARK: Properties
public var video: String?
public var preview: String?
public var clip: String?
public var trailer: Any?
enum CodingKeys: String, CodingKey {
case video = "video"
case preview = "preview"
case clip = "clip"
}
}
from the example we know that trailer
isn't in the codingKeys
yet. You should add all the props to the CodingKeys
. And Any
data type should be changed to codable data type like String
, Int
, or Trailer
(custom codable data type). below is the correct example:
public struct DtClip: Codable {
// MARK: Properties
public var video: String?
public var preview: String?
public var clip: String?
public var trailer: Trailer?
enum CodingKeys: String, CodingKey {
case video = "video"
case preview = "preview"
case clip = "clip"
case trailer = "trailer"
}
}
public struct Trailer: Codable {
// MARK: Properties
public var name: String?
public var id: Int?
enum CodingKeys: String, CodingKey {
case name, url
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With