9

The goal is to subclass SCNNode. According to the class docs, init(geometry geometry: SCNGeometry?) is a designated initializer (no convenience keyword listed), so isn't this code invoking a designated initializer of its superclass?

Why is Xcode showing the following error?

Must call a designated initializer of the superclass SCNNode

class PreviewNode: SCNNode {
    // Constants
    let PreviewNodeColor = gRedColor
    let Size = CGFloat(1.0)
    let ChamferRadius = CGFloat(0.0)

    override init() {
        let previewBox = SCNBox(width: Size, height: Size, length: Size, chamferRadius: ChamferRadius)
        previewBox.firstMaterial!.diffuse.contents = PreviewNodeColor
        previewBox.firstMaterial!.transparency = 0.2
        previewBox.firstMaterial!.specular.contents = UIColor.whiteColor()
        super.init(geometry: previewBox)
    }
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439

3 Answers3

8

The problem there is that you are also trying to access your PreviewNode properties before calling self.init()

Try like this:

Xcode 8 GM • Swift 3

class PreviewNode: SCNNode {
    let previewNodeColor: UIColor = .red
    let size: CGFloat = 1
    let chamferRadius: CGFloat = 0
    convenience override init() {
        self.init()
        let previewBox = SCNBox(width: size, height: size, length: size, chamferRadius: chamferRadius)
        previewBox.firstMaterial?.diffuse.contents = previewNodeColor
        previewBox.firstMaterial?.transparency = 0.2
        previewBox.firstMaterial?.specular.contents = UIColor.white
        self.geometry = previewBox
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • OK so it's a misleading error? Why do you need the `convenience` keyword? Can't you just override the empty init function? – Crashalot Sep 11 '16 at 18:45
  • You need both in this case `convenience override` – Leo Dabus Sep 11 '16 at 18:46
  • Mind explaining why? Trying to understand for future cases. Because it seems to compile fine without the `convenience` keyword. Thanks! – Crashalot Sep 11 '16 at 18:46
  • It is overriding the original empty init which doesn't do that custom geometry . When you add another initializer to that class you need to add a convenience keyword. – Leo Dabus Sep 11 '16 at 18:48
  • It doesn't compile here for me in Playground without the convenience. – Leo Dabus Sep 11 '16 at 18:49
  • If you omit `convenience` and change `self.init` to `super.init` it should compile? – Crashalot Sep 11 '16 at 18:50
  • How would you call `super.init()` and `super.init(geometry:)` twice ? – Leo Dabus Sep 11 '16 at 18:54
  • You don't ... let me post as an answer so you can see it formatted – Crashalot Sep 11 '16 at 18:55
  • @Crashalot kkk thats what I suggested `self.geometry = ` What's the problem about adding convenience keyword there? – Leo Dabus Sep 11 '16 at 18:58
  • 1
    Yes, that's why you will get credit :) But trying to understand if you need `convenience` and `self.init` or whether it's equally valid to do it as posted in the answer. Just trying to understand Swift better! – Crashalot Sep 11 '16 at 18:59
  • No problem with using `convenience` -- just trying to understand if both approaches are equally valid or if one is better. Just trying to understand Swift better, that's all. :) – Crashalot Sep 11 '16 at 19:04
  • Are both equally valid or is your approach better? Could you update the answer with this info for future readers, and then it will get accepted. Thanks again! – Crashalot Sep 11 '16 at 22:26
  • I have never done it calling super, so I can't give my opinion about it. You can do the testing yourself and figure it out. I don't think you will notice any. Feel free to accept the answer that best fits to your needs – Leo Dabus Sep 11 '16 at 22:41
  • OK just accepted your answer. Just curious if you knew the difference and wanted to inform future readers. But if someone with 47K rep isn't certain, it sounds like it's not documented. Thanks again for your help! :) – Crashalot Sep 11 '16 at 22:52
2

Used this answer, but Leo Dabus deserves credit. Please comment if you can explain whether it's equally valid to define a new convenience initializer as Leo did or override the default initializer as this answer does, or if one is preferable to the other.

class PreviewNode: SCNNode {
    // Constants
    let MainColor = gRedColor
    let MainSize = CGFloat(1.0)
    let MainRadius = CGFloat(0.0)
    let MainTransparency = CGFloat(0.2)


    override init() {
        super.init()
        doInit()
    }


    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    private func doInit() {
        let previewBox = SCNBox(width: MainSize, height: MainSize, length: MainSize, chamferRadius: MainRadius)
        previewBox.firstMaterial!.diffuse.contents = MainColor
        previewBox.firstMaterial!.transparency = MainTransparency
        previewBox.firstMaterial!.specular.contents = UIColor.whiteColor()
        self.geometry = previewBox
    }
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439
2

My understanding from the Swift 4.2 Guide is that designated initialisers must initialise all stored properties. Convenience initialisers can initialise a subset of properties (which may be useful in creating instances under different circumstances) however, they must first call a designated initialiser of the subclass that in turn calls the designated initialiser of the superclass(es) to initialise all stored properties before customisation begins. This diagram from the Swift Guide may help:

enter image description here

By using the convenience keyword Leo can then call self.init() which is PreviewNode's designated initialiser which in turn calls SCNNode's designated initialiser. That sorts out the error we both got: 'required' initializer 'init(coder:)' must be provided by subclass of 'SCNNode'. That's because UIView adopts the NSCoding protocol.

Below is a commented version of Leo's code that shows the three options for handling the problem. I think they are all equally correct, but one may suit a particular problem better than others.

import SceneKit
class PreviewNode: SCNNode {
    let previewNodeColor: UIColor = .red
    let size: CGFloat = 1
    let chamferRadius: CGFloat = 0
    //convenience override init() {   //Option 1 - use with self.init() to use the default designated initialisers up the inheritance ladder
    //override init() {               //Option 2 - use with super.init() and the additional required initialiser below
    required init(coder aDecoder: NSCoder) {  //Option 3 - Use with super.init() to initialise the geometry property of SCNNode and the required intialiser
        //self.init()
        super.init()
        let previewBox = SCNBox(width: size, height: size, length: size, chamferRadius: chamferRadius)
        previewBox.firstMaterial?.diffuse.contents = previewNodeColor
        previewBox.firstMaterial?.transparency = 0.2
        previewBox.firstMaterial?.specular.contents = UIColor.white
        self.geometry = previewBox
    }
    //Use with Option 2 to provide the additional required initialiser for NSCoder
    /*
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    */
}

Sorry to bump an old post but I got the same problem and the research was fun :-)

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Tim
  • 1,108
  • 13
  • 25