I do not have any experience with programing. I have done this looking at youtube videos for a couple of months. I will really appreciate if someone can please help me. When I run the code with the simulator it repeats the questions several time before the next new question is presented. I would like it to run so it presents one question without repeating the same question over and over. Below please find the code.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var QuestionLabel: UILabel!
@IBOutlet weak var Button1: UIButton!
@IBOutlet weak var Button2: UIButton!
@IBOutlet weak var Button3: UIButton!
@IBOutlet weak var Button4: UIButton!
@IBOutlet weak var Next: UIButton!
@IBOutlet weak var LabelEnd: UILabel!
var CorrectAnswer = String()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Hide()
RamdomQuestions()
}
func RamdomQuestions () {
var RandomNumber = arc4random() % 4
RandomNumber += 1
switch (RandomNumber) {
case 1:
QuestionLabel.text = "Hola familia, Cual es mi nombre? "
Button1.setTitle ("Cesar", forState: UIControlState.Normal)
Button2.setTitle ("Karlos", forState: UIControlState.Normal)
Button3.setTitle ("William", forState: UIControlState.Normal)
Button4.setTitle ("Chiqui", forState: UIControlState.Normal)
CorrectAnswer = "2"
break
case 2:
QuestionLabel.text = "Hola famili, cual es mi apellido? "
Button1.setTitle ("Perez", forState: UIControlState.Normal)
Button2.setTitle ("Carvajal", forState: UIControlState.Normal)
Button3.setTitle ("Garcia", forState: UIControlState.Normal)
Button4.setTitle ("Sanchez", forState: UIControlState.Normal)
CorrectAnswer = "1"
break
case 3:
QuestionLabel.text = "Quien hace la lachona mas rica? "
Button1.setTitle ("Willy", forState: UIControlState.Normal)
Button2.setTitle ("Mario", forState: UIControlState.Normal)
Button3.setTitle ("Karlos", forState: UIControlState.Normal)
Button4.setTitle ("Juan David", forState: UIControlState.Normal)
CorrectAnswer = "1"
break
case 4:
QuestionLabel.text = "Quien hace las tartas mas lindas"
Button1.setTitle ("Jili", forState: UIControlState.Normal)
Button2.setTitle ("Carvajal", forState: UIControlState.Normal)
Button3.setTitle ("Garcia", forState: UIControlState.Normal)
Button4.setTitle ("Leidy y Liz", forState: UIControlState.Normal)
CorrectAnswer = "4"
break
default:
break
}
}
func Hide (){
LabelEnd.hidden = true
Next.hidden = true
}
func UnHide () {
LabelEnd.hidden = false
Next.hidden = false
}
@IBAction func Button1Action(sender: AnyObject) {
UnHide()
if (CorrectAnswer == "1") {
LabelEnd.text = "Correcto"
}
else{
LabelEnd.text = "Falso"
}
}
func Button2Action(sender: AnyObject) {
UnHide()
if (CorrectAnswer == "2") {
LabelEnd.text = "Correcto"
}
else{
LabelEnd.text = "Falso"
}
}
func Button3Action(sender: AnyObject) {
UnHide()
if (CorrectAnswer == "3") {
LabelEnd.text = "Correcto"
}
else{
LabelEnd.text = "Falso"
}
}
func Button4Action(sender: AnyObject) {
UnHide()
if (CorrectAnswer == "4") {
LabelEnd.text = "Correcto"
}
else{
LabelEnd.text = "Falso"
}
}
@IBAction func Next(sender: AnyObject) {
RamdomQuestions()
}
}
The typical solution for this is to have an array of questions (or indexes into your model) and then shuffle this array so it's random. You can then iterate through this shuffled array of questions, and they'll appear in a largely random fashion, but you don't have to worry about them reappearing.
In Swift 4.2, you'd use the built-in shuffle
or shuffled
methods to shuffle your array.
In Swift versions before 4.2, you have to shuffle the array yourself. Note, when generating a random number, you should not use arc4random
with the %
operator. That introduces modulo bias. Instead, use arc4random_uniform
which generates uniformly distributed random numbers within a range of values. And shuffling the array, you should use a Fisher-Yates algorithm, which eliminates some subtle biases introduced by naive shuffling algorithms. For general information, see the Fisher-Yates article in Wikipedia. For specific Swift implementation, see How do I shuffle an array in Swift?. Anyway, the algorithm looks like:
extension MutableCollection {
/// Shuffle the elements of `self` in-place.
mutating func shuffle() {
if count < 2 { return } // empty and single-element collections don't shuffle
for i in 0 ..< count - 1 {
let j = Int(arc4random_uniform(UInt32(count - i)))
if j != 0 {
let current = index(startIndex, offsetBy: i)
let swapped = index(current, offsetBy: j)
swapAt(current, swapped)
}
}
}
/// Return shuffled collection the elements of `self`.
func shuffled() -> Self {
var results = self
results.shuffle()
return results
}
}
You can then use it like so:
var questionIndexes = Array(0 ..< questions.count) // builds an array [0, 1, 2, ... n-1], where _n_ is the number of questions
questionIndexes.shuffle() // shuffle that list
You end up with an array of the numbers 0 through n-1 that are shuffled (i.e. appear in a random order, but no number occurs more than once). You can now iterate through this array of questionIndexes
and each question will be asked once and only once, but they'll be presented in random order.
A couple of unrelated observations:
You probably want to adopt Cocoa naming conventions, namely that method and property names should always start with a lowercase letter. Only data types (e.g. classes or structs) and enums start with uppercase letters.
When doing a switch
statement in Swift, you don't need break
. Swift doesn't fall through like Objective-C does. When I tackled point 5, below, it turns out I ended up factoring out the switch
statement entirely, but just for your future reference, you don't need to break
at the end of each case
in Swift like you did in C-based programming languages like Objective-C. In fact, if you want a Swift case
statement to fall through to the next one, like it does in Objective-C, you'd have to use the fallthrough
keyword.
You probably shouldn't initialize correctAnswer
as String()
. Just declare it to be an optional (an implicitly unwrapped one, if you want).
Even better, correctAnswer
should be an Int
rather than a String
. I'd also use a zero-based value so that I can easily look up the value to confirm whether the right button was pressed.
This is a more advanced topic, but I'd suggest separating the "model" (i.e. the data about question text, potential answers, and correct answer) from "controller" (the code that takes information from the model and updates the view). This is part of the model-view-controller paradigm that we use in our apps. It makes your app easier to maintain in the future (e.g. you can add more questions, change questions, etc., but not have to touch the code in the view controller). It also enables more flexible patterns (e.g. the questions and answers could be provided by a remote web service or be stored in a database).
For example, you might have a type that captures the question, its potential answer, and identifies which is the correct answer.
struct Question {
let question: String
let answers: [String]
let correctAnswer: Int
}
Your model might then consist of an array of Question
objects:
var questions: [Question] = [
Question(
question: "Hola familia, Cual es mi nombre?",
answers: ["Cesar", "Karlos", "William", "Chiqui"],
correctAnswer: 1),
Question(
question: "Hola famili, cual es mi apellido?",
answers: ["Perez", "Carvajal", "Garcia", "Sanchez"],
correctAnswer: 0),
Question(
question: "Quien hace la lachona mas rica?",
answers: ["Willy", "Mario", "Karlos", "Juan David"],
correctAnswer: 2),
Question(
question: "Quien hace las tartas mas lindas?",
answers: ["Jili", "Carvajal", "Garcia", "Leidy y Liz"],
correctAnswer: 3)
]
Note, forgive my changing the "correct answer" in these questions from that which was presented in your question. I just wanted to illustrate that we're dealing with numbers from 0
through 3
(not 1
through 4
).
Pulling this all together, you might have an implementation that looks like:
import UIKit
struct Question {
let question: String
let answers: [String]
let correctAnswer: Int
}
class ViewController: UIViewController {
@IBOutlet weak var questionLabel: UILabel!
@IBOutlet weak var button1: UIButton!
@IBOutlet weak var button2: UIButton!
@IBOutlet weak var button3: UIButton!
@IBOutlet weak var button4: UIButton!
lazy var buttons: [UIButton] = { return [self.button1, self.button2, self.button3, self.button4] }()
@IBOutlet weak var nextButton: UIButton!
@IBOutlet weak var endLabel: UILabel!
var questions: [Question] = [
Question(
question: "Hola familia, Cual es mi nombre?",
answers: ["Cesar", "Karlos", "William", "Chiqui"],
correctAnswer: 1),
Question(
question: "Hola famili, cual es mi apellido?",
answers: ["Perez", "Carvajal", "Garcia", "Sanchez"],
correctAnswer: 0),
Question(
question: "Quien hace la lachona mas rica?",
answers: ["Willy", "Mario", "Karlos", "Juan David"],
correctAnswer: 2),
Question(
question: "Quien hace las tartas mas lindas?",
answers: ["Jili", "Carvajal", "Garcia", "Leidy y Liz"],
correctAnswer: 3)
]
var questionIndexes: [Int]!
var currentQuestionIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
questionIndexes = Array(0 ..< questions.count) // builds an array [0, 1, 2, ... n]
questionIndexes.shuffle() // randomizes that list
updateLabelsAndButtonsForIndex(0)
}
func updateLabelsAndButtonsForIndex(questionIndex: Int) {
// if we're done, show message in `endLabel` and hide `nextButton`
guard questionIndex < questions.count else {
endLabel.hidden = false
endLabel.text = "All done!"
nextButton.hidden = true
return
}
// update our property
currentQuestionIndex = questionIndex
// hide end label and next button
hideEndLabelAndNextButton()
// identify which question we're presenting
let questionObject = questions[questionIndexes[questionIndex]]
// update question label and answer buttons accordingly
questionLabel.text = questionObject.question
for (answerIndex, button) in buttons.enumerate() {
button.setTitle(questionObject.answers[answerIndex], forState: .Normal)
}
}
func hideEndLabelAndNextButton() {
endLabel.hidden = true
nextButton.hidden = true
}
func unhideEndLabelAndNextButton() {
endLabel.hidden = false
nextButton.hidden = false
}
// note, because I created that array of `buttons`, I now don't need
// to have four `@IBAction` methods, one for each answer button, but
// rather I can look up the index for the button in my `buttons` array
// and see if the index for the button matches the index of the correct
// answer.
@IBAction func didTapAnswerButton(button: UIButton) {
unhideEndLabelAndNextButton()
let buttonIndex = buttons.indexOf(button)
let questionObject = questions[questionIndexes[currentQuestionIndex]]
if buttonIndex == questionObject.correctAnswer {
endLabel.text = "Correcto"
} else {
endLabel.text = "Falso"
}
}
@IBAction func didTapNextButton(sender: AnyObject) {
updateLabelsAndButtonsForIndex(currentQuestionIndex + 1)
}
}
There's a lot buried in there, so I wouldn't worry about the details if you don't quite follow everything that's going on here. But the key is, build an array of indexes into your model, shuffle it, and then you can proceed to iterate through this shuffled array and you're guaranteed that you won't ask the same question twice.