Can only pass data between 2 of 3 View Controllers

Hi,


I´m making a simple orderplacement app. This app uses 3 View Controllers: Menu1, Menu2 and Checkout. (Menu2 was recently added, and is the source of the problem) I pass data between Menu VC an Checkout with a Swift file called orderModel. This has been working perfectly for a long time now, but when I added a new VC, Menu2, I got problems. Menu2 won´t pass data to Checkout VC. I have done the 2 menuVC´s identical. Just with different variables.


Menu1:

class menu1ViewController: UIViewController {

var myOrder = OrderModel()
var tomatoes = "Tomatoes"

@IBAction func item1(_ sender: UIButton) {
myOrder.menu1Tomatoes = tomatoes //Set text to item
myOrder.addToMenu1Tomatoes += 1 //Add amount of tomatoes odered
}
override func viewDidLoad() {
        super.viewDidLoad()
let barViewControllers = self.tabBarController?.viewControllers
let svc = barViewControllers![1] as! checkOutViewController
svc.myOrder = self.myOrder  //shared model
}


Menu2:

class menu2ViewController: UIViewController {
        var myOrder = OrderModel()
  var bacon = "Bacon"

    @IBAction func item20(_ sender: UIButton) {

        myOrder.menu2Bacon = bacon
        myOrder.addToMenu2Bacon += 1
    }
override func viewDidLoad() {
        super.viewDidLoad()
let barViewControllers = self.tabBarController?.viewControllers
let svc = barViewControllers![1] as! checkOutViewController
svc.myOrder = self.myOrder  //shared model
}


CheckOut:

class checkOutViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
    var myOrder: OrderModel?

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
/
        orderLabel.text = myOrder?.currentOrder()
}



OrderModel:


import UIKit
class OrderModel: NSObject {
//item1
var menu1Tomatoes:String = ""
var addToMenu1Tomatoes:Int = 0

//item20
var menu2Bacon:String = ""
var addToMenu2Bacon:Int = 0


//Ready sending to orderLabel in CheckOut
    func item1Tomatoes() -> String {
        if addToMenu1Tomatoes >= 1 {
            return ("\(addToMenu1Tomatoes) \(menu1Tomatoes + "")")
        }
        return ""
    }

func item20bacon() -> String {
        if addToMenu2Bacon >= 1 {
            return ("\(addToMenu2Bacon) \(menu2Bacon + "")")
        }
        return ""
    }

func currentOrder() -> String{ //Return a string with the current order
        return item1Tomatoes() + item20Bacon()
}


The data is passed from menu2 to orderModel. Because if I print(myOrder.item1Tomatoes()) when item20 button is pressed, the data prints correctly.

I hope someone can help me here.

Thank you

Accepted Reply

To create the tab bar:

- in IB, drag a TabBarController to the storyboard

- that will create 2 tabs as well as 2 view Controllers

- declare a class CustomTabBarController

- in the class, declare a var to hold the properties you want to pass between the views ; ex: var myInformation: [String: AnyObject]?

- That will be the "global" var shared between viexs

- set the class of the TabBarController to this Custom Class


- You have declared 2 classes, Menu1ViewController and Menu2ViewController, as you did.

- you declare in IB each VC to belong to the appropriate class ;

- If you want to create a 3rd tab, drag a ViewController to the storyboard

- then control drag from the tabBarController to this viewController and select relationship


In each Menu1ViewController, you can now set or access myInformation with

if let tbc = self.tabBarController as? CustomTabController {
    // do something with tbc.myInformation
}

Note: move the initial view arrow in the story board to the tab bar controller.


All these mechanismes are well detailed in the following post :

h ttps://stackoverflow.com/questions/29734954/how-do-you-share-data-between-view-controllers-and-other-objects-in-swift

Replies

Do you define a tabBarController in each VC ?


How is it you use the same index (1) in menu1 and menu2 ?

     let svc = barViewControllers![1] as! checkOutViewController


It is pretty difficult to understand the logic of your code, but looks like you put in each class elements that should be global to your app, such as

     var myOrder = OrderModel()


So, could you explain how your tabBar is defined ? What in each tab ?

Thank you for your reply.


So, the logic is that i use OrderModel to store tha data from the different VC´s, instead of using global variables. I was told that global variables is not a god idea, and I should a swift-file (orderModel) to store the data.


It´s obviously not correct to use the same index on the two VC´s, but if I don´t the app chrashes. It has been so long since I configured this, that I cannot remember or find back to the way I did it.


In the console, it´s listed that the VS´s have the following order:

[0] = menu1

[1] = menu2

[2] = checkout


The way I added menu2 VC was just drag from tab bar controller to menu2 VC and select "Relationship segue - view controllers"


I´m sure this is a problem whith the indexing, but I can´t figure it out..


Thank you

So, the logic is that i use OrderModel to store tha data from the different VC´s, instead of using global variables. I was told that global variables is not a good idea, and I should a swift-file (orderModel) to store the data.


But you create a new object in each VC. So, you don't store anything !

You should have a single instance of it in your app, otherwise it is useless.


It´s obviously not correct to use the same index on the two VC´s, but if I don´t the app chrashes. It has been so long since I configured this, that I cannot remember or find back to the way I did it.

But what if you pass index 0 instead than 1 ?


In addition, consider this part of your code:

override func viewDidLoad() {
       super.viewDidLoad()
       let barViewControllers = self.tabBarController?.viewControllers
       let svc = barViewControllers![1] as! checkOutViewController
       svc.myOrder = self.myOrder  //shared model
}


You create a var svc

You set its property

But when you leave viewDidLoad, svc disappears.


So there is a problem in the whole logic of your code.

Can you explain how you set it up ?


You have a rootView (the one you see when you launch the app):

- what is it ?

- What is its class

- what objects in it ?

- How do you go to VC1 (menu1ViewController) from here ? And to menu2ViewController

The tabBar should be in this view, calling menu1ViewController and menu1ViewController when click on a tab


I do not see anywhere the class menu1ViewController used anywhere ?

- Is there any instance of this class ?

Note : the name of a class should begin with uppercase Menu1ViewController


May be you should post the complete code, there too much guessing on what you do here.

But what if you pass index 0 instead than 1 ?

-Then the app chrashes

"Could not cast value of type 'Menu2ViewController' (0x10a432f20) to 'CheckOutViewController' (0x10a4317b0)."


So there is a problem in the whole logic of your code.

Can you explain how you set it up ?

-I have to say, that I´m struggeling with this myself. I had a very hard time making this work. I tried lots of different things, and when it finally passed the data from one VC to another I was fed up with the whole thing and moved on. This is quite long ago, so I cannot either explain the logic or how it was set up. I was hoping whith some help with this 🙂


You have a rootView (the one you see when you launch the app):

-It´s a loginscreen with login to Firebase

-UIViewController

-SegmentControl. This let´s the user chose between login and signup. If signup is selected a segue takes the user to a signup form which in turn takes the user to the Tab Bar Controller. If the user chooses login, he puts in username and password and is segued to the Tab Bar Controller.


I´m considering dropping the whole thing and do it all over again. But I´m afraid I will struggle just as much and end up with the same issues. What is a good way to pass data within a Tab Bar Controller?


Thanks alot for your help so far!

To create the tab bar:

- in IB, drag a TabBarController to the storyboard

- that will create 2 tabs as well as 2 view Controllers

- declare a class CustomTabBarController

- in the class, declare a var to hold the properties you want to pass between the views ; ex: var myInformation: [String: AnyObject]?

- That will be the "global" var shared between viexs

- set the class of the TabBarController to this Custom Class


- You have declared 2 classes, Menu1ViewController and Menu2ViewController, as you did.

- you declare in IB each VC to belong to the appropriate class ;

- If you want to create a 3rd tab, drag a ViewController to the storyboard

- then control drag from the tabBarController to this viewController and select relationship


In each Menu1ViewController, you can now set or access myInformation with

if let tbc = self.tabBarController as? CustomTabController {
    // do something with tbc.myInformation
}

Note: move the initial view arrow in the story board to the tab bar controller.


All these mechanismes are well detailed in the following post :

h ttps://stackoverflow.com/questions/29734954/how-do-you-share-data-between-view-controllers-and-other-objects-in-swift

When you say "declare a class CustomTabBarController" do you mean to make a new file called CustomTabBarController" and making it a subclass of UITabBarController?

If so, is there any trics of soing that? I cannot find UITabBarController in the dropdownmenu of "Subclass of"

Or am I misunderstanding?


Thanks

Yes.

I have also noticed that the dropdownmenu does not propose UITabBarController when you start typing UITab ; but if you continue and type UITabBar, it will show UITabBarController !

In any case, you can type the complete name UITabBarController and that will work.

Aha! That worked 🙂


Is it correct to put this code in viewDidLoad?

if let tbc = self.tabBarController as? CustomTabController { 
    // do something with tbc.myInformation 
}


I find it a litte strange to have it in viewDidLoad? But x-code complaints when I try to put it elswhere

You can certainly put it in other func of Menu1ViewController or Menu2ViewController depending of what you want to do. But I have no problem to have it in viewDidLoad.

Ok.

Thank you for all your help! Really awesome.

Could I just bother you with one last question? Or, a tips really.


I want to have each menu item to have one button. This should send a number/counter and a text to the orderLabel in the CheckOutViewController.

Example:

When the user presses the orderButton for Tomatoes, the orderLabel in CheckOutVC should say "1pc Tomatoes" If the user presses the Tomatoes button again, the orderLabel in CheckOutVC should say "2pc Tomatoes"

As you can see from my initial code, I have a solution for this, but it´s not very elegant, and I assume it´s a better way to accomplish this.

How would you go about to solve this, with the new tabBarController solution?


-Thanks again

Tou welcome.


When you say :

I want to have each menu item to have one button.

Do you mean that in each VC, infront of an item of menu, there would be a button to send an order ?

Note: in Cocoa, menuItem is a type of object, different from what we are speaking about here.


I nice way to do this is with observers.


- Outside of the class declaration, to make it global, declare a const:

let kUpdateOrders = "kUpdateOrders"
let tomatoeOrder = 0
let baconOrder = 1
// And so on


- In CheckOutVC, you declare an observer to listen to new orders:

    override func windowDidLoad() {
        super.windowDidLoad()
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(updateOrders(_:)), name:kUpdateOrders, object: nil)

     }

// And remove observer when deinit
    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
  


The action in CheckOutVC is defined like this:

    func updateOrders(sender: NSNotification) {
         guard let userInfo = notification.userInfo else {  return }  // Retrieve the userInfo you have posted from menuItem
         if let orderCount = userInfo["orderCount"], let orderMessage = userInfo["orderText"] {  // Read userInfo dictionary
           switch userInfo {
               case tomatoeOrder :     // Use orderMessage for display, Increment tomato order and update the label to orderCount
               case baconOrder :         // Increment bacon order and update the label to orderCount
               default : break

            }
         }

    }


Connect the order buttons in front of menu items, to their action (I suppose you still have thse func):

For instance:

@IBAction func item1(_ sender: UIButton) {
     myOrder.menu1Tomatoes = tomatoes // Set text to item
     myOrder.addToMenu1Tomatoes += 1 //Add amount of tomatoes odered
     NSNotificationCenter.defaultCenter().postNotificationName(
                        kUpdateOrders, object: self,
                        userInfo:["orderedItem": myOrder.addToMenu1Tomatoes, "orderText": ]) // those who listen will handle
}


The notification is sent ; CheckOutVC listens and will process the notification.