Pass one integer value to multiple view controllers

Hello everyone,


I am new to this forum, and new to Xcode/Swift in general. I've done a bit of research, but didn't find any similar topic. Sorry if the question has been asked already 🙂


So, to make it simple, I am trying to code a quizz app. I have 3 VCs so far :

- ViewController.swift, with a button that redirects to my 1st Quiz

- Quiz1ViewController.swift where 4 questions are asked, and the score is saved into a variable counts "points"

- EndQuiz1ViewController.swift, where the score of my Quiz1 is displayed


I am passing "points" to the endQuiz1VC just fine thanks to the following piece of code in Quiz1VC :


        let destVC = segue.destination as! EndQuiz1ViewController
        destVC.intPassed = points


However, the app totally freaks out when I use the same piece of code to pass my "points" value into MainVC. What other formula do you recommend me to use ?


See full code below for more info.


Thanks in advance,

Mathilde


---- full code ----


/
/
/
/
/
/
/ 
import UIKit
class Quiz1ViewController: UIViewController {
   
    let questions = ["question 1", "question 2", "question 3", "question 4"]
    let answers = [["Bonne réponse", "Mauvaise Réponse", "Mauvaise Réponse", "Mauvaise Réponse"], ["Bonne réponse", "Mauvaise Réponse", "Mauvaise Réponse", "Mauvaise Réponse"], ["Bonne réponse", "Mauvaise Réponse", "Mauvaise Réponse", "Mauvaise Réponse"], ["Bonne réponse", "Mauvaise Réponse", "Mauvaise Réponse", "Mauvaise Réponse"]]
    /
    var currentQuestion = 0
    var rightAnswerPlacement:UInt32 = 0
    var points = Int()
   
    /
    @IBOutlet weak var lbl: UILabel!
   
    /
    @IBAction func action(_ sender: AnyObject)
    {
        if (sender.tag == Int(rightAnswerPlacement))
        {
            print("Right")
            points += 1
        }
        else
        {
            print("Wrong")
        }
        if (currentQuestion != questions.count)
        {
            newQuestion()
        }
        else
        {
            end()
        }

    }
   
    override func viewDidAppear(_ animated: Bool)
    {
        newQuestion()
       
    }
   
   
    /
    func end()
    {
        print(points)
        performSegue(withIdentifier: "showEndQuiz1", sender: self)
    }
   
    override func prepare(for segue:UIStoryboardSegue, sender: Any?)
    {
        let destVC = segue.destination as! EndQuiz1ViewController
        destVC.intPassed = points
          /* let VC = segue.destination as! ViewController
        VC.ScoreQuiz = points */
    }
    /
    func newQuestion()
    {
        lbl.text = questions[currentQuestion]
        rightAnswerPlacement = arc4random_uniform(4)+1
       
        /
        var button:UIButton = UIButton()
        var x = 1
        for i in 1...4
        {
            /
            button = view.viewWithTag as! UIButton
           
            if (i == Int(rightAnswerPlacement))
            {
                button.setTitle(answers[currentQuestion][0], for: .normal)
            }
            else
            {
                button.setTitle(answers[currentQuestion][x], for: .normal)
                x = 2
            }
        }
        currentQuestion += 1
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
        /
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        /
    }
   
    /
    / 
    /
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        /
        /
    }
    */
}

In prepare, you should test which segue you are activating, and do a if let test for the VC :


So change:

    override func prepare(for segue:UIStoryboardSegue, sender: Any?)
    {
        let destVC = segue.destination as! EndQuiz1ViewController
        destVC.intPassed = points
          / let VC = segue.destination as! ViewController
        VC.ScoreQuiz = points */
    }

into

    override func prepare(for segue:UIStoryboardSegue, sender: Any?)
    {
        if (segue.identifier == "showEndQuiz1")  {
             if let destVC = segue.destination as? EndQuiz1ViewController {
                 destVC.intPassed = points
            }
       }
        if (segue.identifier == "SegueToViewController")  {      // Use the identifier you have defined for the segue of course
             if let destVC = segue.destination as? ViewController {
                 destVC.ScoreQuiz = points  
            }
       }
    }



Some comments :

- Why do you declare rightAnswerPlacement as Int32, and not just int ? That would avoid the need to cast to Int.

- the following code is prone to crash, with forced unwrapping ; how is viewWithTag declared ? ARe you sure it can only be a button ?


        var button:UIButton = UIButton()
        var x = 1
        for i in 1...4   { 
            button = view.viewWithTag as! UIButton 
          
            if (i == Int(rightAnswerPlacement))   { 
                button.setTitle(answers[currentQuestion][0], for: .normal)
            }
            else   { 
                button.setTitle(answers[currentQuestion][x], for: .normal)
                x = 2
            }
        }


You could change as :

        // var button:UIButton = UIButton()
        var x = 1          // What is this x value that is changed to 2, and cannot change afterwards
        for i in 1...4   {
            if let button = view.viewWithTag as? UIButton {
                 if (i == rightAnswerPlacement   {     // If rightAnswerPlacement declared as Integer
                     button.setTitle(answers[currentQuestion][0], for: .normal)
                 } else   {
                     button.setTitle(answers[currentQuestion][x], for: .normal)
                     x = 2
                 }
          }
        }

I've been coding in swift xCode for about 10 months now and i've found the easiest way to use a variable across multiple viewcontrollers is to either save the variable to disk and read it back out when needed again or to use a global variable.


Saving and reading from disk.



//This saves to disk. Plcae this in a function when you want to keep a variable for later.
let currentPoints = 100 //Whatever points the user has. This variable is not an integer
//swift/xCode will save the item as the type of the variable. So an integer in this case.
UserDefaults.standard.set(points, forKey: "User_CurrentPoints")


//Then in the other viewcontroller in a function, you can read it back out.
//Notice the forKey is the same here.
let currentPointsFromOtherViewController = UserDefaults.standard.integer(forKey: "User_CurrentPoints")



For variables that you access more then just use a globel variable.



//Define the global variable in any viewcontroller code file but outside of the class. 
//You can even define in the appDelegate as long as you are outside of things

var someVariable = Integer()
//You can now save to or load from this variable from anywhere

class ViewController: UIViewController {
  ///Blah blah your code
}

I may disagree with this statement.


Global variable may be accessed by another thread and modified.


If you pass directly (through prepare(), you are sure of the value you pass to the other controller.


And that's more in line with OO design.

Hi Claude,


Thanks very much !!


I did what you recommended, and it works. But I realize that what I actually want is to save the values. So I tried to save the value in the userDefaults.


However, when i run it, the "scoreComplete" UIlabel (where the data is saved) does not show. (the app does not crash though)

I've read a couple threads where other people describe this issue, but I couldn't draw a parallel with my code. Do you have any clue why it's reacting like this ?


I did thing that way :

1) from QuizVC, where my questions are displayed (PS : i followed this tutorial), I sent the data to my EndQuizVC using segue.

2) I save the data in userdefaults in my EndQuizVC (see below)

class EndQuiz1ViewController: UIViewController {
    @IBOutlet weak var endQuiz1Score: UILabel!
    var intPassed = Int()
    override func viewDidLoad() {
        super.viewDidLoad()
    endQuiz1Score.text = "\(intPassed)"
        func saveScore() {

            let savedString = endQuiz1Score.text
            let userDefaults = Foundation.UserDefaults.standard
            userDefaults.set(savedString, forKey: "Quiz1")
        }
        /
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        /
    }
}


3) I attempt to display the saved data result in my MainVC :

class ViewController: UIViewController
    {
    @IBOutlet weak var buttonQuiz1: UIButton!
    @IBOutlet weak var buttonQuiz2: UIButton!
    @IBOutlet weak var scoreQuiz1: UILabel!
    @IBOutlet weak var scoreComplete: UILabel!
    var ScoreQuiz = Int()
    override func viewDidLoad() {
        super.viewDidLoad()
   print(ScoreQuiz)
        /
    }
    override func viewWillAppear(_ animated: Bool)  {
        let userDefaults = Foundation.UserDefaults.standard
        let value = userDefaults.string(forKey: "Quiz1")
        scoreComplete.text = value
    }
    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
        /
    }
}

On your other questions :

- I declare rightAnswerPlacement as Int32, because the arc4random_uniform(4)+1 function requires it. I can't do it any other way, right ?

- On the piece of code that trigger my questions and answers : basically, my UI has 4 UI buttons, each with a tag and that's how the questions rotate randomly. I followed this tutorial. Is it clearer to you or did I misunderstand what you are saying ? (highly possible option)


Please let me know if you need any other info,


Thanks so much !!

Regards,

Mathilde

Pass one integer value to multiple view controllers
 
 
Q