1

I have a small bit of code that looks like this:

func initAllLabels() {

    var scoreLabels:[[[SKLabelNode]]] = []

    for (var x = 0; x < modesInGame; x++) {
        for (var y = 0; y < levelsInMode; y++) {
            for (z = 0; z < labelsInLevel; z++) {
                scoreLabels[x][y][z] = SKLabelNode(fontNamed: "Font")
            }
        }
    }
}

So what I am trying to do is store all my labels for every game mode. The reason I'm trying to use a multidimensional array is because I will have several labels per level (3-5) and I would like to access them like this:

updateText(scoreLabels[currentMode][currentLevel][thisLabel])

And accessing all the labels for the current label like this:

for label in labelsInLevel:
    label.hidden = false

The problem is that when I try to create all my labels at the start of the game in initAllLabels, I get an "index out of range" error at the first run in the loop (index: 0). I think the problem is because I need to "append" to the array before setting its contents, is this right? How would I accomplish this in an array structure like mine?

1

2 Answers 2

2

You need to initialize the array to a given size before updating items at positions within it. It might help to start with the single-dimensional case:

var labels: [SKLabelNode] = [] // creates an empty array
// since the array is empty, this will generate an index out of range error:
labels[0] = SKLabelNode(fontNamed: "Font")

Instead you need to extend the array with the elements you want to add. For example,

for _ in 0..<labelsInLevel {
    labels.append(SKLabelNode(fontNamed: "Font"))
}

(the _ means “I don’t care about the actual number of each iteration - normally if you wanted to know this was the ith time around the loop you’d write for i in 0..<n)

There are nicer ways to do this though. But be careful with one of them, the initializer for Array that takes a count and a repeatedValue:

let labels = Array(count: labelsInLevel, repeatedValue: SKLabelNode(fontNamed: "Font”))

SKLabelNode is a reference type. That means that a variable only refers to an instance, and assigning one variable to another only copies the reference. So for example:

let variableOne = SKLabelNode(fontNamed: "Foo")
let variableTwo = variableOne  
// variableTwo now refers to the same instance of SKLabelNode
variableTwo.fontName = "Bar"
print(variableOne) 
// fontName will now be “Bar” even though this was
// done via variableTwo

You get the same effect with the repeatedValue code above. The label is created once, and the same reference to it is inserted multiple times. Change a property on one label in the array, and you change them all. Note the for loop version does not have this problem. Every time around the loop, a new SKLabelNode instance will be created. They won’t be shared.

An alternative to the repeatedValue initializer, that creates without using a for loop is to use map:

(0..<labelsInLevel).map { _ in SKLabelNode(fontNamed: "Font") }

Here, just like in the for loop version, a new SKLabelNode instance is created every time.

(again, we use the _ to indicate we don’t care about the number of the iteration)

Finally, to create the nested multidimensional arrays with the loops inside, you can run map multiple times:

var scoreLabels =
  (0..<modesInGame).map { _ in
    (0..<levelsInMode).map { _ in
      (0..<labelsInLevel).map { _ in
        SKLabelNode(fontNamed: "Font")
      }
    }
  }
Sign up to request clarification or add additional context in comments.

1 Comment

Really appreciate the detailed response, had no problems at all with this solution!
-2

You need to initialize the 3D array like this if you want all SKLabelNode to point at different SKLabelNodes :

func initAllLabels() {

    var scoreLabels = [[[SKLabelNode]]](count: modesInGame, repeatedValue:
        [[SKLabelNode]](count: levelsInMode, repeatedValue:
        (0..< labelsInLevel).map { _ in SKLabelNode(fontNamed: "Font") }))
}

The repeatedValue is what you are storing in the cells (type or value) and the count represent the number of times you want to store it in your array.

PS: This answer was tested with Xcode Playground and works perfectly fine.


I edited my answer following Airspeed Velocity's comment.

1 Comment

Note, this creates an inner array with every element referring to the same label instance, because SKLabelNode is a reference type. Changing it will change it in every entry. To create an array of different labels, use something like map: var scoreLabels = Array(count: modesInGame, repeatedValue: Array(count: levelsInMode, repeatedValue: (0..<labelsInLevel).map { _ in SKLabelNode(fontNamed: "Foo") }))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.