0

I have a ViewController that has a UIBarButtonItem on it, I need the data from the TextField (textField  is in the ViewController) to be passed to the TableViewController when the button is clicked without switching to it (TableViewController). ViewController and TableController are associated with TabBarController. ViewController have NavigationController. My version of the code does not work. By clicking on the button, the data is not saved. And in the TableViewController there is only one empty cell.

Image: enter image description here

I hope for your help!

The code in the UIBarButtonItem (in ViewController):

    @IBAction func saveContact(_ sender: UIBarButtonItem) {
        let historyVC = HistroyCallsTableViewController()
        historyVC.userName = self.nameTextField.text ?? "Unknown"
        historyVC.userImage = self.imageView.image
    }

The code in the TableViewController:

struct Contact {
    var title: String
    var image: UIImage
}

class HistroyCallsTableViewController: UITableViewController {
    
    var contacts = [Contact]()
    var userName: String?
    var userImage: UIImage?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let contact = Contact(title: userName ?? "", image: userImage ?? UIImage())
        contacts.append(contact)
    }
    
    // MARK: - Table view data source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return contacts.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? ContactTableViewCell {
            cell.titleContactLabel.text = contacts[indexPath.row].title
            cell.contactPhotoimaveView.image = contacts[indexPath.row].image
            return cell
        }
        return UITableViewCell()
    }
}
SwiftCoder
  • 61
  • 1
  • 8
  • 1
    See [Passing data between view controllers](https://stackoverflow.com/questions/5210535/passing-data-between-view-controllers?r=SearchResults&s=1%7C1197.7998) – HangarRash Nov 04 '22 at 16:10
  • @HangarRash Unfortunately, my case is not considered there – SwiftCoder Nov 04 '22 at 16:24
  • Look for "To pass data back from the second view controller to the first view controller, you use a protocol and a delegate. This video is a very clear walk though of that process:" I guess this is your case from the link: https://stackoverflow.com/questions/5210535/passing-data-between-view-controllers?r=SearchResults&s=1%7C1197.7998 – Allan Garcia Nov 04 '22 at 16:44
  • This here: historyVC.userName = self.nameTextField.text ?? "Unknown" historyVC.userImage = self.imageView.image is considered an anti-pattern. Not following the MVC rules that the VIEW should not be TIED to the VIEWCONTROLLER. The communication from the view (where the userName is) should be a blind communication using delegate, the set of this delegate will require a protocol. Read the part suggested by HangarRash, look for the except that I mentioned. – Allan Garcia Nov 04 '22 at 16:47
  • He's asking A->B, not B->A. Also, your case is pretty well covered in the link from @HangarRash – alessandro_minopoli Nov 04 '22 at 17:02
  • That link discusses the transition between ViewControllers and says nothing about passing data between VC and TableVC, especially using TabBarController. If it was that simple, I wouldn't be asking the question. @alessandro_minopoli – SwiftCoder Nov 04 '22 at 18:15
  • @Denis Look at the accepted answer of that question and scroll down to its "Passing Data Back" section. It's all about using delegates. – HangarRash Nov 04 '22 at 19:24

1 Answers1

1

"I need the data from the TextField (textField is in the ViewController) to be passed to the TableViewController when the button is clicked without switching to it (TableViewController)."

Well, no, you don't.

What you want to do is manage your data.

When you enter a "title and image" in your "Call" view controller, add those values to your "data manager."

When you use the tab bar to switch to your "History" table view controller, populate the table with the data from your "data manager."

Here's a really quick example...

Let's assume we're setup similar to the image you posted:

enter image description here

The CallViewController has two text fields (because you haven't indicated how you are loading a "Contact image"), a "message label", and the "Save" button up in the nav bar.

We start with the Contact struct - I'll use two strings:

struct Contact {
    var title: String
    var imageName: String
}

Now we'll create a Singleton data manager class:

class MyContactData {
    var myContacts: [Contact] = []
    
    static let shared: MyContactData = {
        let instance = MyContactData()
        // setup code
        return instance
    }()
}

In the Call view controller, when we tap the Save button:

class CallViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet var nameTextField: UITextField!
    @IBOutlet var imageTextField: UITextField!

    @IBOutlet var msgLabel: UILabel!

    func textFieldDidBeginEditing(_ textField: UITextField) {
        msgLabel.text = ""
    }
    
    @IBAction func saveContact(_ sender: UIBarButtonItem) {
        print("save")
        
        view.endEditing(true)

        let title = nameTextField.text ?? "Unknown"
        let imgName = imageTextField.text ?? "Unknown"
        
        // create a Contact
        let c = Contact(title: title, imageName: imgName)
        
        // append it to shared data in the data manager singleton
        MyContactData.shared.myContacts.append(c)
        
        // show that we "saved" the data
        msgLabel.text = "Saved:\n\(title)\n\(imgName)"

        // clear the text fields
        nameTextField.text = ""
        imageTextField.text = ""
    }
    
}

and, finally, our History table view controller will look like this:

class HistroyCallsTableViewController: UITableViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        tableView.reloadData()
    }
    
    // MARK: - Table view data source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // our data is in our shared data manager class
        return MyContactData.shared.myContacts.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? ContactTableViewCell {
            
        // our data is in our shared data manager class
            let title = MyContactData.shared.myContacts[indexPath.row].title
            let imgName = MyContactData.shared.myContacts[indexPath.row].imageName
            
            cell.titleContactLabel.text = title
            
            // try to load the image as named asset
            if let img = UIImage(named: imgName) {
                cell.contactPhotoimaveView.image = img
            } else {
                // try to load it as a system image
                if let img = UIImage(systemName: imgName) {
                    cell.contactPhotoimaveView.image = img
                } else {
                    // no image available, so let's use the row number
                    //  as an SF Symbol "indice"
                    if let img = UIImage(systemName: "\(indexPath.row).circle") {
                        cell.contactPhotoimaveView.image = img
                    }
                    
                }
            }

            return cell
        }
        return UITableViewCell()
    }
}

class ContactTableViewCell: UITableViewCell {
    
    @IBOutlet var titleContactLabel: UILabel!
    @IBOutlet var contactPhotoimaveView: UIImageView!
    
}

When we run the app, we'll enter "Bob" and "car":

enter image description here

Tap "Save" and we see this:

enter image description here

Enter "Joe" and "sailboat" ... tap Save:

enter image description here

Tap the Tab icon for History:

enter image description here

Now, most likely, you will want to save -- persist -- the data between app uses? In that case, instead of using the simple MyContactData singleton class as shown, you would use something like Core Data ... but the process would be the same:

  • allow user to enter data
  • on Save tap, save the data via Core Data
  • on navigating to the table view controller, populate it from Core Data
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thank you very much! Everything works, and most importantly, I understood where I was wrong – SwiftCoder Nov 04 '22 at 20:30
  • I'm sorry to trouble you. If it's not difficult for you, I will be glad for your help. How to make it so that by clicking on the TableViewCell the user is thrown back to the first screen in the tabBarController (to the Call view controller) and the values ​​of the cell (the text from the label in History table view controller) appear on the textField (in Call view controller) @DonMag – SwiftCoder Nov 05 '22 at 17:27
  • I used tableview delegate method (didSelectRowAt) but textField remains empty. – SwiftCoder Nov 05 '22 at 17:33
  • to go to the first controller i used property - tabBarController?.selectedIndex = 0 in the delegate method (didSelectRowAt) – SwiftCoder Nov 05 '22 at 17:52
  • @Denis - it's your app, you can design it how you want, but... *in general*, it's a bad idea to switch tabs except when the user taps the tab - it can be confusing navigation. A much more common pattern is: show the list... to add a Contact, either `push` or `present` the "Edit controller" ... on Save (or Cancel), `pop` back to the list. To **edit** a contact, select the contact in the list, instantiate the Edit controller, set the selected Contact in the Edit controller and push or present it. – DonMag Nov 06 '22 at 12:58
  • @Denis - if you ***really*** want to keep your navigation design, you can take a look at this answer: https://stackoverflow.com/a/50536625/6257435 – DonMag Nov 06 '22 at 13:01