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?

Accepted Reply

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

    }

Replies

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!)

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?

Add a Comment

Hi, can you share the whole project, please?