Home > front end >  Swift quiz app how to go on the next question?
Swift quiz app how to go on the next question?

Time:10-09

I'm new to Swift. I'm trying to make a quiz app using the API, I can show a question and answers, but I can't move on to the next questions. I can access different questions in the api with the index, but when I click on the options, I can't go to the next question. I looked at similar applications and watched a few how to make quiz app videos from youtube, but I couldn't do it.

my QuizManager

import Foundation

protocol quizManagerDelegate {
    func didUpdateQuiz(_ Quizmanager: QuizManager ,quiz: QuizModel)
}

struct QuizManager {
    
    var index : Int  = 0
    var maxQuestion = 14

    
    mutating func nextQuestion(result: Bool) -> Int{
        if result == true{
          return  index   1
        } else {
            return index
        }
        
    }
    

    
    var delegate: quizManagerDelegate?
    
    func performRequest(){
        
         let urlString = "https://opentdb.com/api.php?amount=15&type=multiple"        
        if let url = URL(string: urlString){
            
            let session = URLSession(configuration: .default)
            
            let task = session.dataTask(with: url) { data, response, error in

                if error != nil {
                    print(error!)
                    return
                }
                if let safeData = data{
                                        
                    if let quiz = self.parseJSON(quizdata: safeData){
                        delegate?.didUpdateQuiz(self, quiz: quiz)
                        

                    }
                    
                }
            }
            task.resume()
        }
    }
    
    func handle(data: Data?, response: URLResponse?, error: Error?) -> Void {
        
        
    }
    
    func parseJSON(quizdata: Data) -> QuizModel? {
        
        let decoder = JSONDecoder()
        
        do{
            
            let decodedData = try decoder.decode(Welcome.self, from: quizdata)
            
            let correct = decodedData.results?[index].correct_answer ?? "error"
            let quest = decodedData.results?[index].question ?? "error"
            let incorrect = decodedData.results?[index].incorrect_answers ?? ["error"]
            let question = QuizModel(correctAnswer: correct, question: quest, falseAnswer: incorrect)
     
            return question
        } catch {
            print(error)
            return nil
        }
    }
    
    mutating func nextQuestion(){
        
        if index   1 < maxQuestion {
            index  = 1
        }else {
            index = 0
            
        }
    }

}

my QuizData

import Foundation


 // MARK: - Welcome
 struct Welcome: Codable {
 let results: [Result]?
 }
 
 // MARK: - Result
 struct Result: Codable {
     
 let category: String?
 let question, correct_answer: String?
 let incorrect_answers: [String]?
 }

my QuizModel

import Foundation

struct QuizModel {
    
    let correctAnswer : String
    let question : String
    let falseAnswer : [String]
}

my ViewController

import UIKit

class ViewController: UIViewController {
 
    
    @IBOutlet weak var ScoreLabel: UILabel!
    @IBOutlet weak var ChoiceButton4: UIButton!
    @IBOutlet weak var ChoiceButton3: UIButton!
    @IBOutlet weak var ChoiceButton2: UIButton!
    @IBOutlet weak var ChoiceButton1: UIButton!
    @IBOutlet weak var QuestionTextView: UITextView!
    
    var quizMangager = QuizManager()
    var score = 0
    
    var theQuiz: QuizModel?
   
    @IBAction func OptionsButtonPressed(_ sender: UIButton) {
        
        guard let thisQuiz = theQuiz,
                  let btnTitle = sender.currentTitle
            else { return }
            
            if btnTitle == thisQuiz.correctAnswer {
                score  = 1
                ScoreLabel.text = "SCORE: \(score)"
                quizMangager.nextQuestion()
                
                sender.setTitleColor(.systemGreen, for: [])
                
            } else {
                sender.setTitleColor(.systemRed, for: [])
                quizMangager.nextQuestion()
            }
        
        Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
        
        QuestionTextView.text = thisQuiz.question   
        
    }
    
    @objc func updateUI() {
    
        
        
    }
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        QuestionTextView.layer.cornerRadius = 15
        quizMangager.delegate = self
        quizMangager.performRequest()
    }

}

extension ViewController : quizManagerDelegate{
    func didUpdateQuiz(_ Quizmanager: QuizManager, quiz: QuizModel) {
        DispatchQueue.main.async { [self] in
            
            self.theQuiz = quiz

            self.QuestionTextView.text = quiz.question

            var allOptions = []
            allOptions.append(quiz.falseAnswer[0])
            allOptions.append(quiz.falseAnswer[1])
            allOptions.append(quiz.falseAnswer[2])
            allOptions.append(quiz.correctAnswer)
            
            let generatedValue = Array(allOptions.shuffled().prefix(4))
            print(generatedValue)
            print(quiz.correctAnswer)

            ChoiceButton1.setTitle(generatedValue[0] as? String, for: .normal)
            ChoiceButton2.setTitle(generatedValue[1] as? String, for: .normal)
            ChoiceButton3.setTitle(generatedValue[2] as? String, for: .normal)
            ChoiceButton4.setTitle(generatedValue[3] as? String, for: .normal)
        }
    }
}

CodePudding user response:

Because you are "new to Swift" I'll skip critique of your code... but a couple notes...

Since you are using the "new" UIButton styles, you should use the button configuration instead of setTitle() / .setTitleColor(), etc.

To "move on to the next question" add quizManager.performRequest() to your updateUI() function.

Take a look at this modified version of your ViewController class:

class ViewController: UIViewController {
 
    
    @IBOutlet weak var ScoreLabel: UILabel!
    @IBOutlet weak var ChoiceButton4: UIButton!
    @IBOutlet weak var ChoiceButton3: UIButton!
    @IBOutlet weak var ChoiceButton2: UIButton!
    @IBOutlet weak var ChoiceButton1: UIButton!
    @IBOutlet weak var QuestionTextView: UITextView!
    
    var quizMangager = QuizManager()
    var score = 0
    
    var theQuiz: QuizModel?
   
    @IBAction func OptionsButtonPressed(_ sender: UIButton) {
        
        guard let thisQuiz = theQuiz
        else { return }

        // disable buttons so user cannot tap more than one
        let btns: [UIButton] = [ChoiceButton1, ChoiceButton2, ChoiceButton3, ChoiceButton4]
        btns.forEach { btn in
            btn.isUserInteractionEnabled = false
        }

        var cfg = sender.configuration
        
        if let btnTitle = cfg?.title {

            if btnTitle == thisQuiz.correctAnswer {
                score  = 1
                ScoreLabel.text = "SCORE: \(score)"
                cfg?.baseForegroundColor = .systemGreen
            } else {
                cfg?.baseForegroundColor = .systemRed
            }

        }

        sender.configuration = cfg
        
        Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
        
    }
    
    @objc func updateUI() {
    
        quizMangager.nextQuestion()
        quizMangager.performRequest()

    }
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        QuestionTextView.layer.cornerRadius = 15
        quizMangager.delegate = self
        quizMangager.performRequest()
    }

}

extension ViewController : quizManagerDelegate{
    func didUpdateQuiz(_ Quizmanager: QuizManager, quiz: QuizModel) {
        DispatchQueue.main.async { [self] in
            
            self.theQuiz = quiz

            self.QuestionTextView.text = quiz.question

            var allOptions: [String] = []
            allOptions.append(quiz.falseAnswer[0])
            allOptions.append(quiz.falseAnswer[1])
            allOptions.append(quiz.falseAnswer[2])
            allOptions.append(quiz.correctAnswer)
            
            let generatedValue = Array(allOptions.shuffled().prefix(4))
            print(generatedValue)
            print(quiz.correctAnswer)

            let btns: [UIButton] = [ChoiceButton1, ChoiceButton2, ChoiceButton3, ChoiceButton4]
            
            // we want to reset buttons
            //  enabled and title color
            // and set the new titles
            for (str, btn) in zip(generatedValue, btns) {
                
                var cfg = btn.configuration
                cfg?.baseForegroundColor = .white
                cfg?.title = str
                btn.configuration = cfg
                btn.isUserInteractionEnabled = true

            }
            
        }
    }
}

CodePudding user response:

Your code has lots of problems need to be solved.

I will explain as detail as possible and you will need to try it yourself.

First of all, you have QuizManager to control Quiz but you don't store QuizData here you delegate data back to ViewController. So when you update answer you call quizMangager.nextQuestion() is pointless because QuizManager don't control or has any data.

For this you first need to refactor your QuizManager.

  • Change protocol quizManagerDelegate -> protocol QuizManagerDelegate for right naming convention.
  • When you performRequest you get your safeData just parse one time and get the list Result. You can convert to list QuizModel if you like and save it in your QuizManager
  • In QuizManagerDelegate, just notice to the view controller the quizModel in order for updating
protocol QuizManagerDelegate { 
    func didUpdateQuiz(quiz: QuizModel)
}

And in your performRequest code will be like this

self.parseJSON(quizdata: safeData)
delegate?.didUpdateQuiz(quiz:listQuizData[index]) // remember to check if has index

func parseJSON(quizdata: Data) {
    let decoder = JSONDecoder()
    do {
        let decodedData = try decoder.decode(Welcome.self, from: quizdata)
        let decodedResult = decodedData.results // list result
        // you can convert to list quizmodel and save it
        
        // If you do like this just get only one quizModel as each parseJson.
        let correct = decodedData.results?[index].correct_answer ?? "error" // remove
        let quest = decodedData.results?[index].question ?? "error" // remove
        let incorrect = decodedData.results?[index].incorrect_answers ?? ["error"] // remove
        let question = QuizModel(correctAnswer: correct, question: quest, falseAnswer: incorrect) // remove
 
        return question // remove
    } catch {
        print(error)
        return nil
    }
}

with listQuizData is the list result you parse and convert from Json in performRequest

  • Final in your nextQuestion after you update the index just need to call delegate?.didUpdateQuiz(quiz:listQuizData[index])

Come to the ViewController You just need to take the QuizModel and view it

func didUpdateQuiz(quiz: QuizModel) {
    self.theQuiz = quiz
    
    var allOptions : [String] = []
    allOptions.append(contentsOf: quiz.falseAnswer) // combine to append only one time
    allOptions.append(quiz.correctAnswer)
    
    let generatedValue = Array(allOptions.shuffled().prefix(4))
    print(generatedValue)
    print(quiz.correctAnswer)

    ChoiceButton1.setTitle(generatedValue[0], for: .normal)
    ChoiceButton2.setTitle(generatedValue[1], for: .normal)
    ChoiceButton3.setTitle(generatedValue[2], for: .normal)
    ChoiceButton4.setTitle(generatedValue[3], for: .normal)
}
  • Related