the making of quiz app

Click to download project open source on my Github repo :)

Ready or not, here comes your trivia night…

This project uses an MVC design pattern, aka Model, View, Controller. The benefit is mostly about keeping the code clean and readable.

I had some fun selecting the questions to ease you in with some regular fun questions, but little do you know, you are zoning into coding world ;p

There were quite a few challenges in this process:

  • When designing UI Button, the font size won’t show up when you run the app unless you choose “default“ in the “style“ section

  • If you have more than one sound files to use, drag and drop a folder might result in your code not able to track down those files, it will say that your files are missing when running the app. The solution is to manually add them into “Build Phases“ > “Copy Bundle Resources“, just click “+“ from the left bottom corner to add the files into the bundle, this is just to form a link so xcode knows where to look.

  • Making a current question number out of total question number label was some fun challenge to figure out, although there’s a progress bar, but user like myself would love to visually see how many total questions there are and where I am currently, so here’s my solution: (in my case, I have total of 9 questions)

var currentQuestionIndex = 0

func questionProgressUpdate() {
    if currentQuestionIndex < 8 {
        currentQuestionIndex += 1
    } else {
        currentQuestionIndex = 0
    }
    questionProgress.text = "\(currentQuestionIndex + 1)/9"
}

I also had a lot of fun selecting sound effect for user’s right or wrong answer, it’s definitely a creative and rewarding process imagining the in-person reaction I’ll get from people when they hear those funny sound effect they wouldn’t expect. I believe essential human sense stimulation could be a crucial component in user experience, any type of touch button trigger interaction such as sound, animated color or brightness change…anything that keeps users involved, will enhance user engagement.

Below is a snippet of the code and UI:

Here are my assets design:

Clean code with simple notes:

( 1 ) Question.swift — Part One of the files from the Model folder out of the MVC design pattern. The purpose of this file is to create a struct that will hold the questions and answers for the quiz app.

import Foundation

struct Question {
    let text: String
    let answer: String
    
    init(Q: String, A: String) {
        text = Q
        answer = A

    }
}

( 2 ) QuizBrain.swift — Part Two of the .swift files from the Model folder out of the MVC design pattern. The point of using struct here is to create a blueprint for the quiz objects, which will hold the questions and answers, as well as the current question number and score, and the logic for checking the user's answer.

import Foundation

struct QuizBrain {
    
    let questions = [
        
        Question(Q: "Ino is awesome.", A: "True"),
        Question(Q: "The Mona Lisa has no eyebrows.", A: "True"),
        Question(Q: "Australia is wider than the moon.", A: "True"),
        Question(Q: "A group of crows is called a murder.", A: "True"),
        Question(Q: "The first computer bug was a real insect found in a computer.", A: "True"),
        Question(Q: "The Linux operating system is open-source.", A: "True"),
        Question(Q: "Python uses curly braces {} to define code blocks.", A: "False"),
        Question(Q: "Java is an interpreted language.", A: "False"),
        Question(Q: "The 'Git' in Git version control stands for 'Global Information Tracker'.", A: "False")
        
    ]
    
    var questionNumber = 0
    var score = 0
    
    mutating func checkAnswer(userAnswer: String) -> Bool {
        if userAnswer == questions[questionNumber].answer {
            score += 1
            return true
        } else {
            return false
        }
    }
    
    func getScore() -> Int {
        return score
    }
    
    func getQuestionText() -> String {
        return questions[questionNumber].text
    }
    
    func getProgress() -> Float {
        let progress = Float(questionNumber) / Float(questions.count)
        return progress
    }
    
    mutating func nextQuestion() {
        
        if questionNumber + 1 < questions.count {
            questionNumber += 1

        } else {
            questionNumber = 0
            score = 0
            
        }
        
    }
    
}

( 3 ) ViewController.swift — from the Control folder out of the MVC design pattern. The purpose of this file is to create the UI for the quiz app and to connect the UI elements to the QuizBrain model.

import UIKit
import AVFoundation
    
class ViewController: UIViewController {
    
    var player: AVAudioPlayer!
    
    func playSound(named soundName: String) {
           guard let url = Bundle.main.url(forResource: soundName, withExtension: "mp3") else {
               print("Could not find sound file: \(soundName)")
               return
           }
           
           do {
               player = try AVAudioPlayer(contentsOf: url)
               player.play()
           } catch {
               print("Error playing sound: \(error.localizedDescription)")
           }
       }
    
    @IBOutlet weak var questionProgress: UILabel!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var scoreLabel: UILabel!
    @IBOutlet weak var questionText: UILabel!
    @IBOutlet weak var trueButton: UIButton!
    @IBOutlet weak var falseButton: UIButton!
    
    var quizBrain = QuizBrain()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        updateUI()
        questionProgress.text = "1/9"
    }

    @IBAction func answerButtonPressed(_ sender: UIButton) {
        
        let userAnswer = sender.currentTitle!
        let userGotItRight = quizBrain.checkAnswer(userAnswer: userAnswer)
        
        if userGotItRight {
            sender.backgroundColor = UIColor.green.withAlphaComponent(0.6)
            print("green")
            playSound(named: "answerCorrectSound")
        } else {
            sender.backgroundColor = UIColor.red.withAlphaComponent(0.6)
            print("red")
            playSound(named: "answerWrongSound")
        }
        
        questionProgressUpdate()
        
        quizBrain.nextQuestion()
        
        Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
        
    }
    @objc func updateUI() {
        
        questionText.text = quizBrain.getQuestionText()
        progressBar.progress = quizBrain.getProgress()
        scoreLabel.text = "Score: \(quizBrain.getScore())"
        trueButton.backgroundColor = UIColor.systemYellow
        falseButton.backgroundColor = UIColor.systemYellow
        
    }
    
    var currentQuestionIndex = 0
    
    func questionProgressUpdate() {
        if currentQuestionIndex < 8 {
            currentQuestionIndex += 1
        } else {
            currentQuestionIndex = 0
        }
        questionProgress.text = "\(currentQuestionIndex + 1)/9"
    }

}

More detailed notes version of code :

// This app used MVC design pattern, below is the code breakdown separated by the Model and Controller components.
// Model includes two separate .swift files: Question & QuizBrain
// Question.swift code breakdown:

// The purpose of this file is to create a struct that will hold the questions and answers for the quiz app.
// The struct will have two properties: text and answer.
// The struct will have an initializer that will take in two parameters: Q and A.
// The initializer will assign the Q parameter to the text property and the A parameter to the answer property.
// The struct will be used to create question objects in the QuizBrain file.
// This way we don't have a chunck of 'question" and "answer" that looks ugly, instead, we use "Q" and "A", beautiful!

import Foundation

struct Question { // the point of using struct here is to create a blueprint for the question objects
    let text: String 
    let answer: String
    
    init(Q: String, A: String) { // initializer that takes in two parameters: Q and A
        text = Q
        answer = A

    }
}

// QuizBrain.swift code breakdown:

// The point of using struct here is to create a blueprint for the quiz objects, which will hold the questions and answers, 
// as well as the current question number and the score, and the logic for checking the user's answer.
import Foundation

struct QuizBrain {  
    
    let questions = [ 
        
        Question(Q: "Ino is awesome.", A: "True"), 
        Question(Q: "The Mona Lisa has no eyebrows.", A: "True"),
        Question(Q: "Australia is wider than the moon.", A: "True"),
        Question(Q: "A group of crows is called a murder.", A: "True"),
        Question(Q: "The first computer bug was a real insect found in a computer.", A: "True"),
        Question(Q: "The Linux operating system is open-source.", A: "True"),
        Question(Q: "Python uses curly braces {} to define code blocks.", A: "False"),
        Question(Q: "Java is an interpreted language.", A: "False"),
        Question(Q: "The 'Git' in Git version control stands for 'Global Information Tracker'.", A: "False")
        
    ] 

    // questionNumber and score need to start from 0

    var questionNumber = 0  
    var score = 0 
    
    // mutating func here is to check the answers so that the score could progress depending on whether user got the answer right

    mutating func checkAnswer(userAnswer: String) -> Bool { //  
        if userAnswer == questions[questionNumber].answer {
            score += 1
            return true
        } else {
            return false
        }
    }
    
    // access to score

    func getScore() -> Int {
        return score
    }
    
    // access to question text

    func getQuestionText() -> String {
        return questions[questionNumber].text
    }
    
    // access to progress

    func getProgress() -> Float {
        let progress = Float(questionNumber) / Float(questions.count) 
        // Float converts the numbers to decimal numbers so that we can get the progress in percentage
    }
    
    // mutating func here is to progress to the next question

    mutating func nextQuestion() {
        
        if questionNumber + 1 < questions.count {
            questionNumber += 1

        } else {

            // if the user has reached the end of the quiz, the question number and score will reset to 0
            questionNumber = 0 
            score = 0
            
        }
        
    } // end of nextQuestion()
     
} // end of QuizBrain struct

// The code below is the Controller component of the app, which is the ViewController.swift file.
// The purpose of this file is to create the UI for the quiz app and to connect the UI elements to the QuizBrain model.

import UIKit // import UIKit to use UI elements
import AVFoundation // import AVFoundation to play sound files
    
class ViewController: UIViewController { 
    
    var player: AVAudioPlayer! // AVAudioPlayer object that will be used to play sound files.
    
    // playSound function that will play a sound file when the user answers a question.
    // guard let is used to safely unwrap the URL of the sound file, and if the URL is nil, it will print an error message and return.
    // do-catch is used to try to create an AVAudioPlayer object with the URL, and if there is an error, it will print an error message.
    // you can use localizedDescription to get the error message, which is more human readable, thus more user-friendly

           guard let url = Bundle.main.url(forResource: soundName, withExtension: "mp3") else { 
               print("Could not find sound file: \(soundName)")
               return
           }
           
           do {
               player = try AVAudioPlayer(contentsOf: url)
               player.play()
           } catch {
               print("Error playing sound: \(error.localizedDescription)") 
           }
       }
    
    // IBOutlets for the question progress label, progress bar, score label, question text label, true button, and false button.

    @IBOutlet weak var questionProgress: UILabel!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var scoreLabel: UILabel!
    @IBOutlet weak var questionText: UILabel!
    @IBOutlet weak var trueButton: UIButton!
    @IBOutlet weak var falseButton: UIButton!
    
    // QuizBrain object that will be used to manage the quiz logic.
    var quizBrain = QuizBrain()
    
    // set up the initial UI and quiz logic.

    override func viewDidLoad() {
        super.viewDidLoad()
        
        updateUI()
        questionProgress.text = "1/9"
    }

    // answerButtonPressed function that will check the user's answer and update the UI accordingly

    @IBAction func answerButtonPressed(_ sender: UIButton) {
        
        let userAnswer = sender.currentTitle! // currentTitle is used to get the title of the button that was pressed.
        let userGotItRight = quizBrain.checkAnswer(userAnswer: userAnswer) // checkAnswer function is called to check the user's answer.
        
        // if-else statement is used to update the button color and sound effect based on whether the user got the answer right.
        if userGotItRight { 
            sender.backgroundColor = UIColor.green.withAlphaComponent(0.6)
            print("green")
            playSound(named: "answerCorrectSound")
        } else {
            sender.backgroundColor = UIColor.red.withAlphaComponent(0.6)
            print("red")
            playSound(named: "answerWrongSound")
        }
        
        questionProgressUpdate()  // questionProgressUpdate function is called to update the question progress label.
        
        quizBrain.nextQuestion() // nextQuestion function is called to progress to the next question.
        
        Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
        // Timer is used to delay the updateUI function so that the user can see the button color change before the UI updates.

    } // end of answerButtonPressed()

    // updateUI function that will update the question text, progress bar, score label, and button colors.

    @objc func updateUI() { // @objc is used to allow the function to be called by the Timer object.
        
        questionText.text = quizBrain.getQuestionText() 
        progressBar.progress = quizBrain.getProgress()
        scoreLabel.text = "Score: \(quizBrain.getScore())" 
        trueButton.backgroundColor = UIColor.systemYellow 
        falseButton.backgroundColor = UIColor.systemYellow
        
    } // end of updateUI()

    // currentQuestionIndex variable that will keep track of the current question number, starting from 0.
    var currentQuestionIndex = 0
    
    // questionProgressUpdate function that will update the question progress label.
    func questionProgressUpdate() { 
        if currentQuestionIndex < 8 { 
            currentQuestionIndex += 1 
        } else {
            currentQuestionIndex = 0
        }
        questionProgress.text = "\(currentQuestionIndex + 1)/9" 
        // currentQuestionIndex + 1 is used to display the question number starting from 1.

    } // end of questionProgressUpdate()

} // end of ViewController class
Previous
Previous

the making of interactive storyline app “Distopio“

Next
Next

the making of egg timer app