How to pass data through TabBarController?

Hi everyone,


I try to pass data from View Controller through Tab Bar Controller and Navigation Controller to Table View Controller. Screenshot of storyboard is bellow.


https://drive.google.com/open?id=1GW6gaDxfK1zaEOk2aPE8izkS5B67ldZL


My code is next:


1. Send data from View Controller


@IBAction func addToCarButton(_ sender: UIButton) {
       
        tabBarController?.selectedIndex = 1
       
        let navVC = tabBarController?.viewControllers![1] as! UINavigationController//
        let cartTableViewController = navVC.topViewController as! CartTableViewController

        cartTableViewController.titleItem = titleLabel.text
        cartTableViewController.image = SomeImage(photo: imageView.image!)
    }


2. Get data in Table View Controller


import UIKit

class CartTableViewController: UITableViewController {
   
    // MARK: - Store data
    // Create variables for receiving data (title and data from image) from VC
    var titleItem: String?
    var image: SomeImage?
   
    // Create shopping cart - array of selected items
    var cartItems = [Cart]()
    var cartItem: Cart?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillAppear(_ animated: Bool) {
       
        //Load data from archive file
        cartItems = Cart.loadFromFile()
       
        // Create a new item
        if titleItem != nil, image != nil {
            print("titleItem in Cart View Appear - \(String(describing: titleItem))")
            cartItem = Cart(title: titleItem!, image: image!)
        }
       
        // Add new item to shopping cart
        if cartItem != nil {
            cartItems.append(cartItem!)
            
        }
       
        tableView.reloadData()
    }
   

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cartItems.count
    }

   
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       
        let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath)
       
        // Pass title to cell
        cell.textLabel?.text = cartItems[indexPath.row].title
       
        // Retrieve image from array of archive file and convert from Data to UIImage format
       let image = UIImage(data: cartItems[indexPath.row].image.photo)
        // Pass image to cell:
        cell.imageView?.image = image

        return cell
    }
   
   
    // MARK: - Save data to archieve file
    override func viewWillDisappear(_ animated: Bool) {
       
        Cart.saveToFile(cartItems: cartItems)
        tableView.reloadData()
    }
   
    // Clear cart
    @IBAction func clearCartButton(_ sender: UIBarButtonItem) {
        cartItems.removeAll()
        tableView.reloadData()
    }


But data are appear only after second click. I.e., I add item 1 and cart is empty. When I add item 2, cart has item 1. If then I add item 3, card has item 1 and item 2, and go on.


I have done link bellow on gif that shows how it works.


https://drive.google.com/open?id=1_ilwKHXTRBT250zB4At7UsbWJ09fQARl


I tried to use different options, but I can not understand what is happening here.


Could you give me advice how to fix it?

Answered by Claude31 in 369043022

I am not sure how it works, but could you try calling selectedIndex after:


@IBAction func addToCarButton(_ sender: UIButton) {
       
        let navVC = tabBarController?.viewControllers![1] as! UINavigationController
        let cartTableViewController = navVC.topViewController as! CartTableViewController

        cartTableViewController.titleItem = titleLabel.text
        cartTableViewController.image = SomeImage(photo: imageView.image!)
       
        tabBarController?.selectedIndex = 1

    }

How do you transition from the detail (where you pick a bouquet) after taping AddToCart to the tableView ?

This is a transition:


tabBarController?.selectedIndex = 1


And this is a data passing:


let navVC = tabBarController?.viewControllers![1] as! UINavigationController//
let cartTableViewController = navVC.topViewController as! CartTableViewController

cartTableViewController.titleItem = titleLabel.text
cartTableViewController.image = SomeImage(photo: imageView.image!)
Accepted Answer

I am not sure how it works, but could you try calling selectedIndex after:


@IBAction func addToCarButton(_ sender: UIButton) {
       
        let navVC = tabBarController?.viewControllers![1] as! UINavigationController
        let cartTableViewController = navVC.topViewController as! CartTableViewController

        cartTableViewController.titleItem = titleLabel.text
        cartTableViewController.image = SomeImage(photo: imageView.image!)
       
        tabBarController?.selectedIndex = 1

    }

Yes, it works. Thank you very much!


Could I ask you one question more? If you had a task to transfer data in this way, what code would you use?

So, the reason was :

- when you change selectedIndex, you call instantly the destView and its viewWillLoad

- before cartTableViewController.titleItem and image are set

- So, you don't see them at this time

- But then data will be saved


It is the same when you performSegue: you must call it after data are set.


For your code, I would keep it as is.

just change tha name of the func from addToCarButton to addToCartButton 😉


Don't forget to close the thread by marking the corrct answer.

Good continuation.

If you have a lot of data to transfer, you could define a struct to hold it, to transfer in a single call instead of multiple items. It does not change a lot, but easier to use and maintain


Here, would be Cart (f I guess correctly how you defined it


struct Cart {
    var title: String
    var image: SomeImage
}

Then:


@IBAction func addToCarButton(_ sender: UIButton) {

        let navVC = tabBarController?.viewControllers![1] as! UINavigationController//
        let cartTableViewController = navVC.topViewController as! CartTableViewController

        let newItem = Cart(title: titleLabel.text, image; SomeImage(photo: imageView.image!))
        //  cartTableViewController.titleItem = titleLabel.text
        // cartTableViewController.image = SomeImage(photo: imageView.image!)
        cartTableViewController.newItem = newItem
        tabBarController?.selectedIndex = 1

    }


class CartTableViewController: UITableViewController {

    // MARK: - Store data
    // Create variables for receiving data (title and data from image) from VC
    //  var titleItem: String?
    // var image: SomeImage?
    var newItem: Cart?

    // Create shopping cart - array of selected items
    var cartItems = [Cart]()
    // Does not seem to be needed     var cartItem: Cart?

    override func viewDidLoad() {
        super.viewDidLoad()
    }
 
    override func viewWillAppear(_ animated: Bool) {
    
        //Load data from archive file
        cartItems = Cart.loadFromFile()
    
        // Create a new item
/* NOT needed
         if titleItem != nil, image != nil {
            print("titleItem in Cart View Appear - \(String(describing: titleItem))")
            cartItem = Cart(title: titleItem!, image: image!)
        }
*/
    
        // Add new item to shopping cart
        if newItem != nil {

     //    if cartItem != nil {
            // cartItems.append(cartItem!)
            cartItems.append(newItem!)
        }
    
        tableView.reloadData()
    }

"just change tha name of the func from addToCarButton to addToCartButton🙂"


Yeah, and AddToCar is well too 😎

Thank you so much! It is good example. I will study it today.

Great.


I think you can now close this thread.


Wish you good development.

Thankt you one more time. This is a good code improvement

Hello there, you're using some methods called "loadFromFile" and "saveToFile", can you share the implementation with me? thanks in advance.
Sorry, I saw your message just today.

Yes, of course, this is the code.


   
Code Block
// MARK: - Saving Data to Archive File
    
    // 1. Make a path to save and upload data.
    static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    static let archiveUrl = documentsDirectory.appendingPathComponent("classes").appendingPathExtension("plist")
    
    // 2. Encoding and saving data.
    static func saveToFile(classes: [ClassUnit]) {
        let propertyListEncoder = PropertyListEncoder()
        let encodeClasses = try? propertyListEncoder.encode(classes)
        try? encodeClasses?.write(to: archiveUrl, options: .noFileProtection)
        
    }
    // 3. Deconding and load data.
    static func loadFromFile() -> [ClassUnit] {
        let propertyListDecoder = PropertyListDecoder()
        var classes = [ClassUnit]()
        if let retrivedClassesData = try? Data(contentsOf: archiveUrl), let decodeClasses = try? propertyListDecoder.decode(Array<ClassUnit>.self, from: retrivedClassesData) {
            classes = decodeClasses
        }
        return classes
    }

You can see more about this method in Apple's textbook called App Development with Swift. It can be found in iBookStore.

Hi, can you share the whole project, please?

How to pass data through TabBarController?
 
 
Q