TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

Create an Array of IDs from an Array of Structs

Forums > SwiftUI

@DaveC  

Hey everyone! Just joined the forum today, and this is my first post.

I'm not a professional developer, but I'm not brand new to the concepts either having dabbled in the past in several languages, but I'm new to SwiftUI and to iOS development in general. I'm working on my first app which is a trivia app that will present quizzes of 10 random questions from the pool of items that belong to the selected category. I'm still refining the logic, but my thought was to have a struct for the item (id, question, array of choices, etc.). The pool of items would be an array of struct instances. In addition to this, I envisioned a few [Int] arrays to keep track of things such as:

  • availableItems: [Int] would be the id values of the items that have not yet been answered correctly in a previous quiz
  • completedItems: [Int] would be the id values of the items that have been correctly answered on previous quizzes. completedItems.count() would be used to keep a running score total across multiple sessions
  • quiz: [Int] would be the id values of the 10 items randomly selected from availableItems for the current quiz.

Given the following setup (which is a simplified version of what I'm really working on), how would you go about creating the arrays mentioned above? Or would you go about it a different way altogether?

struct TriviaItem {
    var id: Int
    var question: String
    var correctAnswer: String
    var incorrectAnswers: [String]
}

@Observable
class TriviaManager {

    var dataService = DataService()
    var itemPool = [TriviaItem]()
    var quiz = [Int]()
    var completedItems = [Int]()
    var availableItems = [Int]()

    init(dataService: DataService = DataService(),
         itemPool: [TriviaItem] = [TriviaItem](),
         quiz: [Int] = [Int](),
         completedItems: [Int] = [Int](),
         availableItems: [Int] = [Int]()) {

        self.dataService = dataService
        self.itemPool = dataService.getRefranes()
        self.quiz = initQuiz()
    }

func initQuiz() -> [Int]
      // do stuff here
}

3      

@DaveC skips a few critical lessons in the 100 Days of SwiftUI course and jumps to this:

I'm not a professional developer, but I'm not brand new to the concepts either
having dabbled in the past in several languages,
but I'm new to SwiftUI and to iOS development in general (...SNIP...) .....how would you go about creating the arrays mentioned above?

Welcome to Hacking With Swift

@twoStraws has a great program where he lays out basic concepts, builds on that foundation, smashes your assumptions, rebuilds, and introduces SwiftUI concepts along the way.

Your question might indicate that you have some programming chops, but aren't sure of an approach using Swift. I'd encourage you to keep your side project but also FOLLOW THE COURSE.

Partial Answer

You don't want my lecture. Probably just the answer? So here's a hint--> Computed Variable.

You have an array of TriviaQuestions holding ALL the trivia, answered and unanswered. Using a computed variable, you can extract just the unanswered trivia questions from the array and create a computed array.

See-> Array Filters

In your case, you might consider adding a filter() to your TriviaQuestions array, select only unanswered trivia, then return them forming a new, computed variable.

This is a learning site, so please review this example and see how you can apply it to your problem statement. Please return here and share how your solved your coding question!

struct WizardListView: View {
    @State private var wizards = ["Harry", "Hermione", "Ron", "Snape", "Hagrid", "DaveC"]

    // This is a computed variable.
    // It's always calculated whenever it's needed in code. Super efficient.
    var hWizards: [String] {
        wizards.filter{ $0.starts(with: "H") }
    }

    private func createWizardName() -> String {
        let randomInteger = Int.random(in: 100..<200)
        let randomLetter  = Bool.random() ? "H_" : "G_"  // one or the other
        // Create a new random name starting with H or G
        let randomWizard = randomLetter + "Wizard_" + String(randomInteger)
        print ("Random wizard name: \(randomWizard)")
        return randomWizard
    }

    var body: some View {
        VStack {
            Text("Wizard Names starting with H")
                .font(.title2).padding()
            // hWizards is computed each time this view is drawn to screen.
            ForEach(hWizards, id: \.self) { wizard in
                Text("\(wizard)")
            }
            VStack {
                Text("\(wizards.count) Wizards in the list.") // Note the changes
                Text("\(hWizards.count) start with H.")
            }.padding(.vertical)
            Button("Add Random Wizard") {
                wizards.append( createWizardName() ) // add to wizards array
                // Note how the hWizards array is automatically computed.
            }.buttonStyle(.borderedProminent)
        }
    }
}

Keep Coding!

3      

Not sure where the Questions and Answers are stored or got but look like you making to difficult. Started with a struct to hold possible an answer

struct Answer: Identifiable {
    // just so each answer is unique. is required as used in ForEach
    var id = UUID()
    // a string to hold answer text
    var text: String
    // is this correct answer
    var isCorrect: Bool
}

now did a struct to hold a question

struct Question: Identifiable {
    // just so each question is unique it not required but it good habit as SwiftUI like to identify views 
    var id = UUID()
    // a string to hold question text
    var text: String
    // an array to hold answers to question
    var answers: [Answer]
}

now did a class as a data controller

@Observable
class DataController {
    var questions: [Question]

    init() {
        // get questions and answers from your data source
        self.questions = dataSource.shuffled() // <- this take place as now it will be random questions
    }
}

Added couple of properties

// count the number of questions
var questionCount = 0
// change when count has reached a certain number
var gameOver = false

Just added some example data to the class

//example data source
let dataSource: [Question] = [
    Question(text: "question1", answers: [Answer(text: "answer1", isCorrect: false),
                                          Answer(text: "answer2", isCorrect: false),
                                          Answer(text: "answer3", isCorrect: true),
                                          Answer(text: "answer4", isCorrect: false),
                                          Answer(text: "answer5", isCorrect: false),
                                         ]),
    :
    // few of questions to 10
    :
    Question(text: "question10", answers: [Answer(text: "answer1", isCorrect: false),
                                          Answer(text: "answer2", isCorrect: false),
                                          Answer(text: "answer3", isCorrect: false),
                                          Answer(text: "answer4", isCorrect: true),
                                          Answer(text: "answer5", isCorrect: false),
                                         ]),
]

added a couple of computed propeties to make showing in ContentView easier

var questionText: String {
    questions[questionCount].text
}

var answers: [Answer] {
    questions[questionCount].answers
}

Add a method to chack answer and to check if number of questions

func check(_ answer: Answer) {
    if answer.isCorrect {
        print("Correct")
    } else {
        print("Wrong!")
    }
    questionCount += 1
    questions.remove(at: 0)

    if questionCount >= 3 {
        gameOver = true
        // start a new game (but do check if you have enough question in array. or save!
        if questionCount < questions.count - 3 {
            // start
        }
    }
}

So this is the ContentView

struct ContentView: View {
    @State private var dataController = DataController()

    var body: some View {
        VStack {

            Text(dataController.questionText)

            ForEach(dataController.answers) { answer in
                Button {
                    dataController.check(answer)
                } label: {
                    Text(answer.text)
                }
            }
        }
        .padding()
        .fullScreenCover(isPresented: $dataController.gameOver) {
            Text("Game Over")
                .font(.largeTitle)
        }
    }
}

And Datacontroller

@Observable
class DataController {
    var questions: [Question]
    var questionCount = 0
    var gameOver = false

    var questionText: String {
        questions[questionCount].text
    }

    var answers: [Answer] {
        questions[questionCount].answers
    }

    func check(_ answer: Answer) {
        if answer.isCorrect {
            print("Correct")
        } else {
            print("Wrong!")
        }
        questionCount += 1
        questions.remove(at: 0)

        if questionCount >= 3 {
            gameOver = true

            if questionCount < questions.count - 3 {
                // start
            }
        }
    }

    init() {
        // get questions and answers from your data source
        self.questions = dataSource.shuffled()
    }

    //example data source
    let dataSource: [Question] = [
        Question(text: "question1", answers: [Answer(text: "answer1", isCorrect: false),
        :
        :
}

I have done this with a app Bus Quizzes which has over 1360 questions (with four answer to each question).

PS you could go even better is to make the Array of questions a Set as there no order in a Set

3      

@DaveC  

Thanks @Obelix and @NigelGee! I appreciate the quick replies. I will spend some time with your suggestions and see what I can do. I know that I have a lot to learn, and having a project is the best way to do it. My only purpose in mentioning that I'm not a complete noob was just so that the suggestions wouldn't be to point me to tutorials about "what is a string?" or "what are funcitons and methods?" So, as long as it's helping learn stuff I don't already know, I'm open to whatever "lectures," examples, or other guidance you have to offer.

My first thought was to do have the 10-question quiz be an actual array of TriviaItem structs instead of just the id values. That seems like it would be simpler. Perhaps some additional explanation of the logic would help. The idea is that there are going to be several categories with a pool of 500+ items per category. The user will select a category and take a 10-question quiz. Whichever questions they answer correctly are "flagged" so that they are no longer included in future quizzes and are added to the total score. When a user exits the app, I need to persist the score and which questions were answered correctly, so that when they come back, their score is intact, and the next quiz will not include any questions they already answered. With the need to persist that info, I figured it would be better to persist an array of id values instead of persisting an array of all the structs for the questions that had been completed.

Anyway, this is still in its infancy, and I'm at the very beginning of my SwiftUI/iOS development journey, so I appreciate all the coaching, guidance, and even lecturing I can get.

Thanks again, and I'll let you know what I come up with.

3      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

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

Reply to this topic…

You need to create an account or log in to reply.

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.