Array Index out of Range confusion

62 Views Asked by At

I have an array of objects defined in swift, I load it in viewDidLoad(). The following code prints out the the values as expected:

for pair in pairs {
    print("Pair - \(pair.word1): \(pair.word2)")
}

and the result looks like:

Pair - left: links
Pair - das Pferd: horse
Pair - good-bye: Auf Wiederzehn
Pair - right: rechts
Pair - horse: das Pferd
Pair - devil: der Teufeld
Pair - hello: Guten Tag
Pair - down: nach unten
Pair - nach unten: down
Pair - rechts: right
Pair - der Teufeld: devil
Pair - hoch: up
Pair - up: hoch
Pair - links: left
Pair - Auf Wiederzehn: good-bye
Pair - Guten Tag: hello

But the very next line causes an error of "Fatal error:Index out of range":

print("Test print - \(pairs[0].word1")

How can this code work fine in the for loop, but crashes with array index out of range if I try to access pairs[0].word1 immediately after this code?

Full code follow:

import UIKit
class ViewController: UIViewController {
    var pairs = [Pair]()
    var wordButtons = [UIButton]()
    override func viewDidLoad() {
        super.viewDidLoad()
        guard let url = Bundle.main.url(forResource: "pairs", withExtension: "json") else {return}
        do {
            let jsonData = try Data(contentsOf: url)
            let jsonDecoder = JSONDecoder()
            pairs = try jsonDecoder.decode([Pair].self, from: jsonData)
            pairs.shuffle()
        } catch {
            fatalError("Can't decode")
        }
        loadLevel()
        for pair in pairs {
            print("Pair - \(pair.word1): \(pair.word2)")
        } // Prints fine
    }
    override func loadView() {
        view = UIView()
        view.backgroundColor = .darkGray
        titleLabel = UILabel()
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.textAlignment = .center
        titleLabel.text = "Concentration"
        titleLabel.font = .systemFont(ofSize: 20, weight: .bold)
        titleLabel.textColor = .white
        view.addSubview(titleLabel)
        let cardsView = UIView()
        cardsView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(cardsView)
        NSLayoutConstraint.activate([
            scoreLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            scoreLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
            cardsView.widthAnchor.constraint(equalToConstant: 400),
            cardsView.heightAnchor.constraint(equalToConstant: 750),
            cardsView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            cardsView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 50)
        ])
        let width = 100
        let height = 150
        for row in 0..<4 {
            for column in 0..<4 {
                let wordButton = UIButton(type: .custom)
                wordButton.titleLabel?.font = UIFont.systemFont(ofSize: 12)
                wordButton.setTitle("WWW", for: .normal)
                wordButton.setImage(UIImage(named: "cardBack"), for: .normal)
                wordButton.addTarget(self, action: #selector(wordTapped), for: .touchUpInside)
                let frame = CGRect(x: column * width, y: row * height, width: width, height: height)
                wordButton.frame = frame
                wordButton.layer.borderWidth = 5
                wordButton.layer.borderColor = UIColor.lightGray.cgColor
                cardsView.addSubview(wordButton)
                wordButtons.append(wordButton)
            }
        }
        for pair in pairs {
            print("Pair - \(pair.word1): \(pair.word2)")
        }
        print("Crash \(pairs[0].word1)")
    }
    func loadLevel() {
        // this is the new code that works fine
        for i in 0..<pairs.count {
            print("Setting title of wordButtons = \(i) title = \(pairs[i].word1)")
            wordButtons[i].setTitle(pairs[i].word1, for: .normal)
        }
    }
    //... rest of code removed due to relevance
}
1

There are 1 best solutions below

0
Rob Napier On

You've very likely sliced this array, so the first index is not 0 (and pairs is probably an ArraySlice instead of an Array). Try this instead:

print("Test print - \(pairs.first!.word1)")

I expect that will work. This kind of problem occurs if you do something like:

let pairs = originalData[2..<10]

In this case, the first index of pairs is 2, not 0. As a rule, you should not subscript with numeric literals. Fetch indexes from the collection (firstIndex(of:) and the like), or use collection accessors like first.