How can I make a swift multiple questions quiz so

2019-07-18 21:17发布

问题:

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

回答1:

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:

  1. 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.

  2. 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.

  3. You probably shouldn't initialize correctAnswer as String(). Just declare it to be an optional (an implicitly unwrapped one, if you want).

  4. 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.

  5. 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.