UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day35 Challenge - Appending to array does not work

Forums > 100 Days of SwiftUI

Hey all,

I'm trying the Day 35 challenge an having some severe problems with accessing an array. It seems to me that I'm missing some important concepts. I created an empty array and trying to fill it with the questions to display for the game. It looks like this:

struct ContentView: View {

...

@State var gameQuestions = [Question]()

var body: some View {

      VStack {

      ...

          HStack(spacing: 50) {
                Button(action: {
                    StartLearningPressed()
                }) {
                    ...
                }
      }

}

// Game was started
func StartLearningPressed() {

    ...

    // Fill question Array
    for categoryIndex in 0 ..< categories.count {
        if( categories[categoryIndex].isSelected ) {
            for questionIndex in 0 ..< categories[categoryIndex].questions.count {
                gameQuestions.append(categories[categoryIndex].questions[questionIndex])
                print("Question added: \(categories[categoryIndex].questions[questionIndex].questionString)")
            }
        }
    }

...

}

...

}

What I'm trying to do is to start the game on button press. At this point I know which tables have to be learned (called categories). For each category I pre-generated all questions and answers. Now I want to put all question objects to the gameQuestions array using an append. The code compiles, the game runs, but there is no object put into the array. The append does not work, neither does insert or something. I can access all the Questions objects and its questionString, as the print statement shows them all correctly on the console.

Can you help me to understand which concepts I'm missing here regarding the usage of the array, the @State and working with it?

Best regards Andy

2      

You could try

gameQuestions.append(categories[categoryIndex].questions[questionIndex] as Question)

Your gameQuestions are an array of Question not categories[categoryIndex].questions[questionIndex], unless Question is defined as an [ ].[ ]

2      

Thank you, just tried it exactly this way. Unfortunately it does not work. Same problem, the array stays empty.

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Put a breakpoint on the Print statement then print out in the debug window categories[categoryIndex].questions[questionIndex] using po categories[categoryIndex].questions[questionIndex].

Does the structure match Question?

2      

Instead of all that looping over indices, do something like this:

for category in categories {
    if category.isSelected {
        gameQuestions.append(contentsOf: category.questions)
    }
}

2      

@Greenamberred: Yes, it prints out Question and its content. Thanks for the po advice. I didn't know it yet.

@roosterboy: Thanks for your suggestion. It makes the code much more readable an shorter. Great takeaway for me. Unfortunately this also does not work, as gameQuestions still stays empty.

2      

I'm thinking there has to be something going on in the code you didn't post because both my suggestion and that from @Greenamberred should have worked.

Can you post your entire ContentView?

2      

@roosterboy: Yes, no problem. It compiles and the app can be started. It´s still quite dirty coded, maybe there lies some hidden problem. Here we go:

import SwiftUI

struct ContentView: View {

    // let totalCategories = 12
    let totalQuestions = [5, 10, 20, 120]
    @State private var numberOfQuestions = 5
    @State var isGameStarted = false
    @State var showingScore = false
    @State var typing = false
    @State var correctAnswerGiven = false
    @State var gameOver = false
    @State var currentQuestion = "Spiel nicht gestartet ..."
    @State var currentAnswer = ""
    @State var correctAnswer = ""
    @State private var scoreTitle = ""
    @State private var currentScore = 0
    @State var categories = GenerateCategories()
    @State var gridLayout: [GridItem] = [GridItem(),GridItem(),GridItem(),GridItem()]
    @State var gameQuestions = [Question]()
    @State var testArray = [Int]()

    var body: some View {

        VStack {
            Text("Bitte wähle die Kategorien:")
                .padding()
                .font(.title)

            LazyVGrid(columns: gridLayout, alignment: .center, spacing: 10) {
                ForEach(0..<categories.count) { categoryIndex in
                    Button(action: {
                        CategorySelected(categoryIndex: categoryIndex)
                    }) {
                        Text("\(categoryIndex+1)x1")
                            .fontWeight(.bold)
                            .font(.system(size: 20  ))
                            .padding(10)
                            // .background(LinearGradient(gradient: Gradient(colors: [Color.orange, Color.red]), startPoint: .leading, endPoint: .trailing))
                            .background(!categories[categoryIndex].isSelected ?
                                LinearGradient(gradient: Gradient(colors: [Color.orange, Color.red]), startPoint: .leading, endPoint: .trailing) :
                                            LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .leading, endPoint: .trailing) )
                            .cornerRadius(40)
                            .foregroundColor(.white)
                            .padding(10)

                            /*
                            .overlay(
                                RoundedRectangle(cornerRadius: 40)
                                    .stroke(Color.orange, lineWidth: 5)
                            )
                            */
                    }
                    .shadow(radius: 1.0)
                }
            }

            Form {
                Section(header: Text("Wie viele Fragen möchtest Du?")) {
                    Picker("Fragen", selection: $numberOfQuestions) {
                        ForEach(0 ..< totalQuestions.count) {
                            Text("\(self.totalQuestions[$0])")
                        }
                    }
                }
                .pickerStyle(SegmentedPickerStyle())

                Section(header: Text("Frage:")) {
                    Text("\(currentQuestion)")
                }

                Section(header: Text("Antwort:")) {
                    TextField("", text: $currentAnswer, onEditingChanged: {
                        self.typing = $0
                    }, onCommit: {
                        AnswerEntered()
                    })
                }
            }

            // Button Controls
            HStack(spacing: 50) {
                Button(action: {
                    StartLearningPressed()
                    // self.gameQuestions = StartLearningPressed()
                    // NextQuestion()
                }) {
                    Text("Start")
                        .padding()
                        .foregroundColor(.black)
                        .background(Color.green)
                        .cornerRadius(40)
                        .overlay(
                            RoundedRectangle(cornerRadius: 40)
                                .stroke(Color.black, lineWidth: 1)
                        )
                }

                Button(action: {
                    Reset()
                }) {
                    Text("Reset")
                        .padding()
                        .foregroundColor(.black)
                        .background(Color.red)
                        .cornerRadius(40)
                        .overlay(
                            RoundedRectangle(cornerRadius: 40)
                                .stroke(Color.black, lineWidth: 1)
                        )
                }
            }

            Spacer()

        } // Outer VStack
        .alert(isPresented: $showingScore) {
            Alert(title: Text(scoreTitle), message: Text("Deine Punktezahl ist \(currentScore)"), dismissButton: .default(Text("Weiter")) {
                if( isGameStarted ) {
                    self.NextQuestion()
                }
                else {
                    gameOver = true
                }
            })
        }
        .alert(isPresented: $gameOver) {
            Alert(title: Text(scoreTitle), message: Text("Game Over - Deine Punktzahl: \(currentScore)"), dismissButton: .default(Text("Ende")) {
                Reset()
            })
        }

    } // body some:View

    // If a category was selected, change its selection state
    func CategorySelected(categoryIndex: Int) {
        print("Category selected: \(categoryIndex)")

        if(!categories[categoryIndex].isSelected) {
            categories[categoryIndex].isSelected = true
        }
        else {
            categories[categoryIndex].isSelected = false
        }
    }

    // Game was started
    func StartLearningPressed() {
        isGameStarted = true;

        // Fill question Array
        for category in categories {
            if category.isSelected {
                gameQuestions.append(contentsOf: category.questions)
            }
        }

        /*
        for categoryIndex in 0 ..< categories.count {
            if( categories[categoryIndex].isSelected ) {
                for questionIndex in 0 ..< categories[categoryIndex].questions.count {
                    // let element = categories[categoryIndex].questions[questionIndex]
                    gameQuestions.append(categories[categoryIndex].questions[questionIndex] as Question)
                    print("Question added: \(categories[categoryIndex].questions[questionIndex].questionString)")
                }
            }
        }
        */

        // Shuffle question Array
        gameQuestions.shuffle()

        // Present next question
        NextQuestion()

    }

    // Game must be resetted
    func Reset() {
        // Todo: Reset all content
        isGameStarted = false
        gameOver = false
        scoreTitle = ""
        currentScore = 0
        showingScore = false
        correctAnswerGiven = false
        currentAnswer = ""
        correctAnswer = ""
        numberOfQuestions = 5
        currentQuestion = "Spiel nicht gestartet ..."
        if( !gameQuestions.isEmpty ) {
            gameQuestions.removeAll()
        }
        for categoryIndex in 0 ..< categories.count {
            if( categories[categoryIndex].isSelected ) {
                categories[categoryIndex].isSelected = false
            }
        }
    }

    func AnswerEntered() {
        print("Your answer was: \(currentAnswer)")

        let result = Int(currentAnswer)
        let correctResult = Int(correctAnswer)

        if( result == correctResult )
        {
            correctAnswerGiven = false
            currentScore += 1
        }

        if( !gameQuestions.isEmpty ) {
            gameQuestions.remove(at: 0)
        }
        else
        {
            isGameStarted = false
        }

        showingScore = true
    }

    // Ask the next question
    func NextQuestion() {
        currentAnswer = ""

        if( !gameQuestions.isEmpty && numberOfQuestions > 0 ) {
            currentQuestion = gameQuestions[0].questionString
            correctAnswer = gameQuestions[0].answerString
            numberOfQuestions = -1
        }
        else {
            // Todo: Do some error handling
            gameOver = true
        }
    }

    // Creates all the categories
    static func GenerateCategories() -> [Category] {
        var categories = [Category]()

        for i in 1...12 {
            categories.append(Category(category:i))
        }

        return Array(categories[0..<12])
    }
} // Content View

// Represents a table category like 1x1
struct Category {
    var categoryNumber: Int
    var questions: [Question] = [Question]()
    var isSelected: Bool = false

    init(category: Int) {
        self.categoryNumber = category
        for i in 1...10 {
            questions.append(Question(categoryIndex: categoryNumber, question: i))
        }
    }
}

// Represents a question
struct Question {
    var categoryCode: Int
    var question: Int
    var answer: Int
    var questionString: String
    var answerString: String

    init(categoryIndex: Int, question: Int) {
        self.categoryCode = categoryIndex
        self.question = question
        self.answer = categoryCode * question
        self.questionString = "What is \(categoryCode)x\(question)?"
        self.answerString = "The correct answer is \(answer)"
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2      

When I use that code and stick a print(gameQuestions) line immediately after gameQuestions is filled, I see the expected output:

output in debug pane of filling gameQuestions array

So gameQuestions is getting filled, but there are issues elsewhere that prevent the game from playng through.

  1. In the NextQuestion function, you have this line in order to decrement the number of questions the user has remaining in the game:
numberOfQuestions = -1

But this doesn't decrement numberOfQuestions, it sets it to -1. Which means when you check a few lines above if numberOfQuestions > 0, the check will fail and the game will end.

It should be:

numberOfQuestions -= 1

This will decrease the number of questions remaining until the user has gone through them all. This is called a compound assignment operator and it is equivalent to:

numberOfQuestions = numberOfQuestions - 1
  1. You use numberOfQuestions to both count how many questions the user has remaining and as the selection value of the Picker for how many questions they want. This means when you decrement numberOfQuestions after each question is answered, your Picker selection will jump around. Probably not what you intended. You should store these separately.

Make those two changes and see if your game functions the way you were expecting.


Also, as just a stylistic thing, the Swift standard is to use names that start with a lowercase letter for functions and variables, and names that start with uppercase letters for types. You have used names like CategorySelected and StartLearningPressed for your functions. This can be confusing when others read your code, like in the line:

CategorySelected(categoryIndex: categoryIndex)

For someone reading this code for the first time, this looks like you are initializing a type called CategorySelected, not calling a function.

Your functions should have names like categorySelected and startLearningPressed. But, like I said, this is a stylistic thing. If you don't change your function names, nothing will be wrong in your code and it will still run. There will just be a little mental friction for someone besides yourself who reads your code.

It also helps to name your functions with verb forms, so selectCategory instead of CategorySelected.

3      

One other thing you have two alerts on the VStack. Currently SwiftUI does not handle more than one alert on a view, so you have to be creative. Hopefully that limitation will be removed in future releases of SwiftUI, we shall see.

In the meantime you could try moving each alert to a separate button - as suggested here.

3      

@roosterboy: Thank you so much for your help. You're right, printing the gameQuestions Array outputs it contents and shows it's not empty. Still I wonder, why the XCode Debugging View for Variables shows that the array is empty.

Regarding the numberOfQuestions decrement, you're absolutely right. It was an ugly typo. Thanks for pointing it out. I also will use another variable for the Picker and the "questions left" counting. I'll take those two changes into account and see how the game behaves.

Also many thanks for the hint on naming of functions. I wasn't aware of this. I want to learn the coding styleguide of Swift, thus I'll change my functions to lower case. In my workplace we are using Upper Case for functions, but not in a iOS / Swift environment.

@Greenamberred: Great suggestion, I wasn't aware of this. I'll take it into account an refactor this point.

2      

Just a short update. Thanks to your help I finally got this game running. I learned a ton of new stuff.

2      

That's great to hear!

It might help others find the answers if you mark this as solved.

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.