简体   繁体   中英

Swift: “Must call a designated initializer of the superclass” error even though code is doing so

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)
    }
}

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
    }
}

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
    }
}

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:

在此输入图像描述

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 :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM