skip to Main Content

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)
        }
    }
}

2

Answers


  1. 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)
    }
    
    Login or Signup to reply.
  2. 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
    
                }
                
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search