Conform struct with UIImage to Codable

I have a data model with UIImage, Color and TextAlignment in.
I think I have managed to conform Color and TextAlignment to Codable.

I am unsure of how to conform UIImage to Codable.
I have an attempt below which crashes when the app loads, probably because there is something wrong with line 20 because backgroundImage is initially nil.
Code Block Swift
struct Model: Codable {
private enum CodingKeys: CodingKey {
case image
}
var backgroundColor: Color
var textColor: Color
var textAlignment: TextAlignment
var image: UIImage?
init(backgroundColor: Color = Color(.systemFill), textColor: Color = Color(.label), textAlignment: TextAlignment = .leading, image: UIImage? = nil) {
self.backgroundColor = backgroundColor
self.textColor = textColor
self.textAlignment = textAlignment
self.image = image
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let data = try container.decode(Data.self, forKey: .image)
try self.init(from: decoder)
self.image = UIImage(data: data)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let image = self.image {
let data = image.jpegData(compressionQuality: 1.0)
try container.encode(data, forKey: .image)
}
}
}


Can anyone correct or point me in the right direction as how to fix this?
If is anything wrong with the Codable conformations below, please let me know.

Any help would be really appreciated.


Color and TextAlignment Codable below:
Code Block Swift
extension Color: Codable {
private enum CodingKeys: CodingKey {
case color
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self = try container.decode(Color.self, forKey: .color)
try self.init(from: decoder)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self, forKey: .color)
}
}
extension TextAlignment: Codable {
private enum CodingKeys: CodingKey {
case center
case leading
case trailing
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let decoded = try? container.decode(String.self, forKey: .center), decoded == "center" {
self = .center
}
if let decoded = try? container.decode(String.self, forKey: .leading), decoded == "leading" {
self = .leading
}
if let decoded = try? container.decode(String.self, forKey: .trailing), decoded == "trailing" {
self = .trailing
}
try self.init(from: decoder)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .center:
try container.encode("center", forKey: .center)
case .leading:
try container.encode("leading", forKey: .leading)
case .trailing:
try container.encode("trailing", forKey: .trailing)
}
}
}

Answered by OOPer in 639048022

Can anyone correct or point me in the right direction as how to fix this?
If is anything wrong with the Codable conformations below, please let me know.

What is critically bad in your code is that calling init(from:) from inside init(from:).

Line 22 of Model and line 9 and line 38 of Color and TextAlignment Codable.
If you call init(from:) from inside init(from:), that causes infinite recursion and then crash.


An example of making TextAlignment conform to Codable:
Code Block
extension TextAlignment: Codable {
private enum TextAlignmentRawValue: Int, Codable {
case leading
case center
case trailing
init(_ textAlignment: TextAlignment) {
switch textAlignment {
case .leading:
self = .leading
case .center:
self = .center
case .trailing:
self = .trailing
}
}
var textAlignment: TextAlignment {
switch self {
case .leading:
return .leading
case .center:
return .center
case .trailing:
return .trailing
}
}
}
public init(from decoder: Decoder) throws {
let rawValue = try TextAlignmentRawValue(from: decoder)
self = rawValue.textAlignment
}
public func encode(to encoder: Encoder) throws {
let rawValue = TextAlignmentRawValue(self)
try rawValue.encode(to: encoder)
}
}



there is something wrong with line 20 because backgroundImage is initially nil.

The crash may be caused by infinite recursion as I said above, but your line 20 may also cause runtime issue.
You should use decodeIfPresent when you know the key may not exist in the encoded data.
Accepted Answer

Can anyone correct or point me in the right direction as how to fix this?
If is anything wrong with the Codable conformations below, please let me know.

What is critically bad in your code is that calling init(from:) from inside init(from:).

Line 22 of Model and line 9 and line 38 of Color and TextAlignment Codable.
If you call init(from:) from inside init(from:), that causes infinite recursion and then crash.


An example of making TextAlignment conform to Codable:
Code Block
extension TextAlignment: Codable {
private enum TextAlignmentRawValue: Int, Codable {
case leading
case center
case trailing
init(_ textAlignment: TextAlignment) {
switch textAlignment {
case .leading:
self = .leading
case .center:
self = .center
case .trailing:
self = .trailing
}
}
var textAlignment: TextAlignment {
switch self {
case .leading:
return .leading
case .center:
return .center
case .trailing:
return .trailing
}
}
}
public init(from decoder: Decoder) throws {
let rawValue = try TextAlignmentRawValue(from: decoder)
self = rawValue.textAlignment
}
public func encode(to encoder: Encoder) throws {
let rawValue = TextAlignmentRawValue(self)
try rawValue.encode(to: encoder)
}
}



there is something wrong with line 20 because backgroundImage is initially nil.

The crash may be caused by infinite recursion as I said above, but your line 20 may also cause runtime issue.
You should use decodeIfPresent when you know the key may not exist in the encoded data.
I didn't know about decodeIfPresent otherwise I would have used it.

If I remove the line
Code Block Swift
self.init(from: decoder)

I get an error: Return from initializer without initializing all stored properties
so I replaced it with this:
Code Block Swift
self.init()

so now init(from:) looks like:
Code Block Swift
let container = try decoder.container(keyedBy: CodingKeys.self)
self.init()
if let data = try container.decode(Data.self, forKey: .image){
self.image = UIImage(data: data)
}

It works now with now crashes, but is this the right thing to do: call the custom init from init(from:) or do you have to manually do
Code Block Swift
self.background = .red
self.text = .blue
...

do you have to manually do

Yes, you have to.
I will test with this now and mark your answer as correct @OOPer if it works. Thanks for the help again.
Conform struct with UIImage to Codable
 
 
Q