SKLabelNode into dictionary

Trying to get an SKLabelNode (SpriteKit) into a dictionary I keep getting the error “Cannot use instance member ‘labelRatingA’ within property initializer; property initializers run before 'self' is available”, I can however get it into an array by initialising it with parenthesis at the end, but not the dictionary:

var normalSeasonGUISKLabelNodes: [String : SKLabelNode] = [“test1” : labelRatingA]
var labelArray: [SKLabelNode] = [labelRatingA, labelRatingB]() 

Hi! Compiler shows you an error, because compiler cannot guarantee that labelRatingA exists and is valid. There's nothing saying that it should create labelRatingA before normalSeasonGUISKLabelNodes.

To fix that you need to add lazy before the var to make it so normalSeasonGUISKLabelNodes is initialized only when you try to access it for the first time.

lazy var normalSeasonGUISKLabelNodes: [String : SKLabelNode] = [“test1” : labelRatingA]

Btw, the array example doesn't compile either, but there was a compiler bug that made it so compiler didn't know on which line the error happened. It seems to be fixed in 5.1

1 Like

Thanks for the information, I should read up and try to remember about the use of lazy. I didn’t realise about the bug, I’m stuck on v4.2 anyway.

While the error did disappear, it only returned when the later on anyway. Perhaps I am taking the wrong approach:

enum NormalSeasonGUISKLabelNodes: String {

case labelRatingA: “labelRatingA”
case labelRatingB: “labelRatingB”
}

        lazy var normalSeasonGUISKLabelNodes: [NormalSeasonGUISKLabelNodes.RawValue : SKLabelNode] = [NormalSeasonGUISKLabelNodes.labelRatingA.rawValue : labelRatingA]
        //var labelArray: [SKLabelNode] = [labelRatingA, labelRatingB]()
    

        func confirmNodesPresentv2() {
            
            for (key, value) in normalSeasonGUISKLabelNodes {
                
                var labelNode: SKLabelNode = value
                
                self.enumerateChildNodes(withName: "//*") {                                 
                    node, stop in
                    
                    if let theCamera: SKCameraNode = node as? SKCameraNode {                 
                        
                        self.camera = theCamera
                        
                        if theCamera.childNode(withName: key) is SKLabelNode {
                            
                            self.labelNode = theCamera.childNode(withName: key) as! SKLabelNode
                            self.labelNode.text = ""
                        }
                    }
                }
            }
        }

The code I am trying to replace is too verbose and repetitive:

func confirmNodesPresentv1() {
        
        self.enumerateChildNodes(withName: "//*") {                                 
            node, stop in
            
            if let theCamera: SKCameraNode = node as? SKCameraNode {                 
                
                self.camera = theCamera
                
                if theCamera.childNode(withName: "labelRatingA") is SKLabelNode {
                    
                    self.labelRatingA = theCamera.childNode(withName: "labelRatingA") as! SKLabelNode
                    self.labelRatingA.text = ""
                }
	}
	}
}

..and the checks just repeat there on.

At first I thought about iterating over an enum, but while I could replace the string values easily enough, I couldn’t force cast them into an SKLabelNode to mimic the variables. Therefore I decided to use a dictionary, as you know, but run into the error already mentioned. I also thought about converting to a generic function so that I could pass in both SKLabelNode and SKSpriteNode, collating the dictionaries and just nesting them.

Each way still allows for the code to be read and managed as the data logically correlates, but neither approach tried of which I have managed to get to work.

Your approach seems good to me at the first sight. Could you paste the whole file, correctly indented, with the comment on which line the error appears, and what error it is? In the code you pasted I cannot see where you defined labelRatingA.

Another error is that when defining enum cases in swift you need to use = and not :
I'm assuming that fancy unicode around your strings are copy-paste errors. If you're using them in your code, you must change them to plain ascii ones: "

Oh, and default rawValue for an enum case is the name of that case, so you can probably not write them at all

Sorry, I did edit in Notes, but the code will compile minus the rookie syntax errors :)

The error I get is: ":0: error: cannot use instance member 'labelNode' within property initializer; property initializers run before 'self' is available"

And if I reference the variable (SKLabelNodes) with self. I get the following error:

Value of type 'GameScene' has no member 'labelNode'

The only missing think is a declaration of a variable:

    var labelRatingA: SKLabelNode = SKLabelNode()

You haven't shown me how your code looks like, so I have to guess. Compiler Explorer It compiles without any errors.

Apologies, the sun got to me today and I wasn't thinking properly. It got to my iMac too, it shut itself down, so I'm on nights.

Thinking about how I had so many different checks there was no one size fits all, but the commonality between them was the initial check to see if the node existed, so I created a function to streamline that, accepting a dictionary and returning a bool value for each, of which then the dedicated function could then make the specific changes:

import SpriteKit
import GameplayKit

enum NormalSeasonGUISKLabelNodes: String, CaseIterable {
    
    case labelRatingA
    case labelRatingB
    case labelRatingC
    case labelRatingD
    case labelRatingE
}

enum NormalSeasonGUISKSpritelNodes: String, CaseIterable {
    
    case ratingButtonA
    case ratingButtonB
    case ratingButtonC
    case ratingButtonD
    case ratingButtonE
}

    var labelRatingA: SKLabelNode = SKLabelNode()
    var labelRatingB: SKLabelNode = SKLabelNode()
    var labelRatingC: SKLabelNode = SKLabelNode()
    var labelRatingD: SKLabelNode = SKLabelNode()
    var labelRatingE: SKLabelNode = SKLabelNode()

    var ratingButtonA: SKSpriteNode = SKSpriteNode()
    var ratingButtonB: SKSpriteNode = SKSpriteNode()
    var ratingButtonC: SKSpriteNode = SKSpriteNode()
    var ratingButtonD: SKSpriteNode = SKSpriteNode()
    var ratingButtonE: SKSpriteNode = SKSpriteNode()


    lazy var normalSeasonGUISKLabelNodes: [NormalSeasonGUISKLabelNodes.RawValue : SKLabelNode] =
            [
            NormalSeasonGUISKLabelNodes.labelRatingA.rawValue : labelRatingA,
            NormalSeasonGUISKLabelNodes.labelRatingB.rawValue : labelRatingB,
            NormalSeasonGUISKLabelNodes.labelRatingC.rawValue : labelRatingC,
            NormalSeasonGUISKLabelNodes.labelRatingD.rawValue : labelRatingD,
            NormalSeasonGUISKLabelNodes.labelRatingE.rawValue : labelRatingE,
            ]
    
    lazy var normalSeasonGUISKSpritelNodes: [NormalSeasonGUISKSpritelNodes.RawValue : SKSpriteNode] =
            [
            NormalSeasonGUISKSpritelNodes.ratingButtonA.rawValue : ratingButtonA,
            NormalSeasonGUISKSpritelNodes.ratingButtonB.rawValue : ratingButtonB,
            NormalSeasonGUISKSpritelNodes.ratingButtonC.rawValue : ratingButtonC,
            NormalSeasonGUISKSpritelNodes.ratingButtonD.rawValue : ratingButtonD,
            NormalSeasonGUISKSpritelNodes.ratingButtonE.rawValue : ratingButtonE,
            ]


    //if node is present then associate it with GUI element and return true value
    func nodesPresent(nodesList: [String : Any], forClass: AnyClass) -> Bool {   
        
        var returnValue: Bool = false
        
        for (key, value) in nodesList {    
            var object = value as! SKLabelNode   //cannot get forClass parameter accepted
            
            self.enumerateChildNodes(withName: "//*") {
                node, stop in
                
                if let theCamera: SKCameraNode = node as? SKCameraNode {
                    
                    self.camera = theCamera
                    
                    if theCamera.childNode(withName: key) is SKLabelNode {
                        
                        object = theCamera.childNode(withName: key) as! SKLabelNode
                        print("node present : \(key)")
                        returnValue = true
                        
                    } else {
                        print("node missing : \(key)")
                        returnValue = false
                    }
                }
            }
        }
        return returnValue
    }


    //setup the main nodes for camera GUI: both labels and buttons, performing specific actions
    func setupMainGUINodes() {
        
        //setup label nodes
        for (key, value) in normalSeasonGUISKLabelNodes {
            if nodesPresent(nodesList: ([key : value]), forClass: SKLabelNode.self) == true {
                value.text = ""
            }
        }
        
        //setup button nodes
        for (key, value) in normalSeasonGUISKSpritelNodes {
            if nodesPresent(nodesList: ([key : value]), forClass: SKSpriteNode.self) == true {
                for spriteNode in NormalSeasonGUISKSpritelNodes.allCases {
                    switch spriteNode {
                    case .ratingButtonA : ratingButtonA.isHidden = true
                    case .ratingButtonB : print("")    
                    case .ratingButtonC : print("")
                    case .ratingButtonD : ratingButtonD.isHidden = true; ratingButtonD.name = "egg"
                    case .ratingButtonE : ratingButtonE.isHidden = true; ratingButtonE.name = "bacon"
                    }
                }
            }
        }
    }

This allows me the flexibility to cut down on the repetitive code, performing the uncommon changes whenever needed, whilst still being readable.

The only issue was the "forClass" parameter, which I could not get it to accept, that will obviously need to change if it's to be of general use.

nb. if you know of a way to do nothing on a case statement (I use empty print) please let me know. The case statements must be exhaustive, so I have to put it in, even when I don't need to add any more changes to something.

Use generics for that:

func nodesPresent<C>(nodesList: [String: Any], forClass: C.Type) -> Bool {
    // ...
    var object = value as! C
1 Like

I read about how this Swift approaches this as a language, but I didn't understand and had enough on my plate.

Thank you for the shortcut!

Use the break statement.

1 Like

Thank you.