I'm trying to subclass a Codable
class and it's working fine until I add a init(from decoder: Decoder)
function. Then, the compiler is giving me 2 errors on my convenience init:
Cannot infer contextual base in reference to member 'geometry'
Extra arguments at positions #2, #3, #4, #5 in call
If I remove the decode function, I get no errors and the encode function works as expected. How can I have both a convenience init and a decoder function? Is there some unwritten rule prohibiting this?
class GeometryNode: Node {
var values = GeometryNode.Values(shape: .triangle)
enum CodingKeys: String, CodingKey {
case values
case id
case type
case indexPath
}
// NOTE: GeometryNode.Values is a codable struct
// NOTE: GeometryNode.Values.Shape a codable enum
// NOTE: both are defined elsewhere
convenience init(id: String? = nil, shape: GeometryNode.Values.Shape = GeometryNode.Values.Shape.triangle, indexPath: IndexPath) {
let icon = shape.icon
let color = shape.color
let title = shape.rawValue
// Error shows for below call
self.init(id: id, title: title, type: .geometry, icon: icon, color: color, indexPath: indexPath)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.values, forKey: .values)
}
// Remove this function and error above goes away
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
values = try container.decode(GeometryNode.Values.self, forKey: .values)
}
}
extension GeometryNode {
struct Values: Codable {
var shape: Shape
enum Shape: String, CaseIterable, Codable {
case triangle
case rectangle
case oval
var defaultColor: UIColor {
return NodeType.geometry.defaultColor
}
var fontSize: CGFloat {
return 24
}
var icon: UIImage {
return miscValues.icon
}
var color: UIColor {
return miscValues.color
}
private var miscValues: (icon: UIImage, color: UIColor) {
switch self {
case .triangle: return ("📐".textToImage(fontSize: fontSize)!, color: defaultColor)
case .rectangle: return ("◾️".textToImage(fontSize: fontSize)!, color: defaultColor)
case .oval: return ("⚫️".textToImage(fontSize: fontSize)!, color: defaultColor)
}
}
}
enum CodingKeys: String, CodingKey {
case shape
}
}
}
public class Node: Codable {
var id: String?
var title: String?
var type: NodeType = .geometry
var icon: UIImage?
var color: UIColor?
var indexPath: IndexPath
var defaultColorForType: UIColor {
return type.defaultColor
}
var absoluteCoordinates: CGPoint? {
return CGPoint(x: indexPath.item * kCellWidth, y: indexPath.section * kCellHeight)
}
func dictionary() -> [String: Any] {
let data = (try? JSONEncoder().encode(self)) ?? Data()
return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:]
}
enum CodingKeys: String, CodingKey {
case id
case type
case indexPath
}
internal init(id: String? = nil, title: String, type: NodeType = .geometry, icon: UIImage? = nil, color: UIColor? = nil, indexPath: IndexPath) {
self.id = id
self.title = title
self.type = type
self.icon = icon
self.color = color
self.indexPath = indexPath
}
}
extension IndexPath {
enum CodingKeys: String, CodingKey {
case item
case section
}
}
If you don't really need a convenience initializer then you can call super.init
inside that initializer, like this:
init(id: String? = nil, shape: GeometryNode.Values.Shape = GeometryNode.Values.Shape.triangle, indexPath: IndexPath) {
let icon = shape.icon
let color = shape.color
let title = shape.rawValue
super.init(id: id, title: title, type: .geometry, icon: icon, color: color, indexPath: indexPath)
}
If you still need the initializer to be convenience
you may need to override the super's initializer because Swift only allows convenience initializers to call designated initializers from the same class.
convenience init(id: String? = nil, shape: GeometryNode.Values.Shape = GeometryNode.Values.Shape.triangle, indexPath: IndexPath) {
let icon = shape.icon
let color = shape.color
let title = shape.rawValue
self.init(id: id, title: title, type: .geometry, icon: icon, color: color, indexPath: indexPath)
}
override init(id: String? = nil, title: String, type: NodeType = .geometry, icon: UIImage? = nil, color: UIColor? = nil, indexPath: IndexPath) {
super.init(id: id, title: title, type: type, icon: icon, color: color, indexPath: indexPath)
}