Personally I would combine the two @IBAction
s into a single one and identify the pressed button from the sender object.
I would also recommend you to look into the Ternary Conditional Operator
, which can make your simple if
statements much cleaner, like the following:
if currentQuestion.answer == true {
answerLabel.text = "correct"
} else if currentQuestion.answer == false {
answerLabel.text = "Incorrect"
}
// The previous is exactly the same as the following:
answerLabel.text = (currentQuestion.answer == true) ? "correct" : "Incorrect"
You can read more about the Ternary Conditional Operator here.
You can see my result from trying to recreate your current setup as inspiration (FYI, my buttons have the titles "True" and "False", which I make use of to turn them into booleans):
//
// ViewController.swift
// TrueFalseQuiz
//
// Created by Stefan Veis Pennerup on 06/06/15.
// Copyright (c) 2015 Kumuluzz. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
// MARK: - Question models
// Notice: This is not the best practice for storing the model in terms of the MVC pattern,
// this is just for illustration purposes.
let questions = [
QuizQuestion(question: "Do I like coffee?", answer: true, explanation: "Because it's awesome!"),
QuizQuestion(question: "Is bacon god's gift to mankind?", answer: true, explanation: "Because it's awesome!"),
QuizQuestion(question: "Should I take a nap right now?", answer: false, explanation: "You gotta review some code!")]
var currentQuestionIndex = 0
// MARK: - Storyboard outlets
@IBOutlet weak var questionLabel: UILabel!
@IBOutlet weak var answerLabel: UILabel!
// MARK: - Lifecycle methods
override func viewDidLoad() {
super.viewDidLoad()
// Initializes the first question
self.answerLabel.text = ""
self.questionLabel.text = questions[currentQuestionIndex].question
}
// MARK: - Storyboard actions
/**
The target action for both the true false buttons
*/
@IBAction func answerPressed(sender: UIButton) {
// Exits if there aren't any questions left
if currentQuestionIndex >= questions.count { return }
// Retrieves the user's answer and figures out if it correct
let userAnswer = sender.currentTitle
let isAnswerCorrect = userAnswer?.toBool() == questions[currentQuestionIndex].answer
// Prints appropiate message
answerLabel.text = (isAnswerCorrect) ? "Correct" : "Incorrect"
// Updates with a new question
currentQuestionIndex++
let isThereAnyQuestionsLeft = currentQuestionIndex < questions.count
questionLabel.text = (isThereAnyQuestionsLeft) ? questions[currentQuestionIndex].question : "No more questions"
}
}
// Extends the standard String class to convert a string to a boolean
extension String {
func toBool() -> Bool? {
switch self {
case "True", "true", "yes", "1":
return true
case "False", "false", "no", "0":
return false
default:
return nil
}
}
}
Could you describe a bit more about the "previous question" button? Should it replace the current question? Should it show the user's answer or just start over with the question?
UPDATE 1:
This update is concerning the "previous question" funcionality outlined by OP in the comments of this answer.
Regarding next and back:
I would add two new buttons (a "next" and "back" button, surprise, surprise) and create two separate storyboard actions for each of them. Each method would respectively increment or decrement the current question index. Both of the methods would then update the currently displayed question.
// MARK: - Next/back methods
@IBAction func nextPressed(sender: UIButton) {
currentQuestionIndex++
if currentQuestionIndex == questions.count { currentQuestionIndex = 0 }
updateCurrentQuestion()
}
@IBAction func backPressed(sender: UIButton) {
currentQuestionIndex--
if currentQuestionIndex < 0 { currentQuestionIndex = questions.count - 1 }
updateCurrentQuestion()
}
func updateCurrentQuestion() {
answerLabel.text = ""
questionLabel.text = questions[currentQuestionIndex].question
}
Regarding submitting all:
In the code outline aboved there are 2 main varibles related to your questions model:
let questions: [QuizQuestion]
var currentQuestionIndex: Int
To handle the feature for storing the user's answers, then I would add another property to your QuizQuestion
model to hold the user's answers. This means your model would look something similar to the following:
//
// QuizQuestion.swift
// TrueFalseQuiz
//
// Created by Stefan Veis Pennerup on 06/06/15.
// Copyright (c) 2015 Kumuluzz. All rights reserved.
//
import Foundation
class QuizQuestion {
let question: String!
let answer: Bool!
let explanation: String!
var usersAnswer: Bool?
init(question: String, answer: Bool, explanation: String) {
self.question = question
self.answer = answer
self.explanation = explanation
}
}
Since usersAnswer
is an Optional, then it will initially be nil
. Whenever the method answerPressed
is called, then you should add the following line to store the answer, thus overwriting nil
:
let userAnswer = sender.currentTitle
questions[currentQuestionIndex].usersAnswer = userAnswer?.toBool()
I would then add a new button called "finish" or "submit all" and connect the following storyboard action, which determines if all the questions have been answered before continuing.
// MARK: - Finishing
@IBAction func submitAll(sender: UIButton) {
let hasAnsweredAllQuestions = questions.reduce(true) { (x, q) in x && (q.usersAnswer != nil) }
println("has user answered all questions?: \(hasAnsweredAllQuestions)")
}
UPDATE 2:
Explanation of reduce
for OP.
reduce
can be quite hard to grasp initially, but once you understand it, then it can be super powerful. In short, it takes all the values of an array and compute it into a single value (could be an Int
, Bool
or something else). You can read more about it here: https://stackoverflow.com/questions/28283451/ios-swift-reduce-function.
The line
let hasAnsweredAllQuestions = questions.reduce(true) { (x, q) in x && (q.usersAnswer != nil) }
could be expanded to something similar the following:
var hasAnsweredAllQuestions = true
for q in questions {
if (q.usersAnswer != nil) {
hasAnsweredAllQuestions = true
}
else {
hasAnsweredAllQuestions = false
break;
}
}
As you can see it simply loops over all the questions and checks if the user has given an answer (if the answer is not nil).
Other functions which are equally powerful as reduce
are map
and filter
which you can try to do more research on.
Regarding checking the user's answer with the correct answer, then you could consider using map
, but I'll leave you with that hint for now ;)