-1

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
    }
}
zakdances
  • 22,285
  • 32
  • 102
  • 173
  • 1
    Can you please provide a full source code or create a sample to demonstrate your problem? With some hidden information, it's hard for people to understand your issue. – congnd Aug 12 '20 at 23:39
  • @CongNguyen ok, I added the superclass and structs/enums – zakdances Aug 13 '20 at 00:49

2 Answers2

1

It’s not “unwritten”. It’s a major fact about Swift. As soon as you add an explicit non-convenience initializer, inheritance of initializers stops operating. So you are trying to call an initializer that doesn’t exist.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Ah, right...forget that `init(from decoder: Decoder)` would stop all init inheritance. Thank you. Ordered your book btw. – zakdances Aug 13 '20 at 02:11
0

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)
}
congnd
  • 1,168
  • 8
  • 13