Search code examples
swiftviewcontrollerdelegatesprotocols

protocol delegates in swift to multiple views


I am trying to learn all I can about delegates. In this example the main page is called PagesTableViewController. When user clicks Page 1, it takes the to DetailsViewController. When you fill in label you go back to PagesTableViewController. then when Button is pushed it takes you to PrintViewController and shows the label filled in from DetailsViewController. I have been trying tutorials and something in the code I must be doing wrong.

Here is my view

enter image description here

Here is code for PagesTableView

import UIKit

class PagesTableViewController: UIViewController {

    let pages = ["Page 1","Page 2","Page 3"]

    @IBOutlet weak var pagesTableView: UITableView!


    override func viewDidLoad() {
        super.viewDidLoad()

        pagesTableView.dataSource = self

    }

    @IBAction func printBtnPressed(_ sender: Any) {

    }


}

extension PagesTableViewController: UITableViewDataSource {

    // MARK: - Table view data source

     func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return pages.count
    }

      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     let cell = tableView.dequeueReusableCell(withIdentifier: "PagesCell", for: indexPath)

     cell.textLabel?.text = pages[indexPath.row]

     return cell
     }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "DetailsViewSegue" {

        }
    }

}

DetailsViewController

import UIKit

class DetailsViewController: UIViewController {

    @IBOutlet weak var detailsTextField: UITextField!

    var childDelegate: basicChildInfo?

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func cancelBtnPressed(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }

    @IBAction func saveBtnPressed(_ sender: Any) {

        let data = detailsTextField.text
        childDelegate?.userEnteredChildInfo(data: data!)
        dismiss(animated: true, completion: nil)

    }
}

PrintViewController

import UIKit

class PrintViewController: UIViewController, basicChildInfo {

    @IBOutlet weak var printLabelOne: UILabel!


    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    //protocol func pulled from child info -- Data from child info
    func userEnteredChildInfo(data: String) {
        printLabelOne.text = data
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "PrintSegue" {
            let childInfoVC: DetailsViewController = segue.destination as! DetailsViewController
            childInfoVC.childDelegate = self
        }
    }

    @IBAction func printBtnPressed(_ sender: Any) {

        print("Printing File")
        dismiss(animated: true, completion: nil)

    }

}

And Lastly my protocol. File On on swift file

import Foundation

protocol basicChildInfo {
    func userEnteredChildInfo(data: String)
}

Solution

  • My guess is that the delegate on DetailsViewController never gets set. Here's the sequence of your UI's control flow (as far as I understand it):

    1. a new DetailsViewController is created and presented
    2. the user enters text
    3. the user taps the Save button
    4. DetailsViewController informs its delegate about the text and dismisses itself, thus getting deleted
    5. a new PrintViewController is created and presented

    So at the time when DetailsViewController wants to inform its delegate about the entered value, that delegate has not yet been set; actually the PrintViewController does not even exist at this time.

    What's worse, the code in PrintViewController's prepare-for-segue most likely never gets called because there is no segue from the Print VC to the Details VC, and this method only is run when a segue-based transition occurs.


    So, coming back to the problem you want to solve: I suggest that you...

    • let PagesTableViewController conform to the delegate protocol and set it as delegate when DetailsViewController is presented
    • in the delegate callback, store the entered data in a member variable of PagesTableViewController
    • when PrintViewController is presented, set the value stored in that member var on a property of PrintViewController

    So both actions -- setting the delegate on DetailsViewController and configuring the entered text on PrintViewController -- go into PrintViewController's prepare-for-segue:

    class PagesTableViewController: UIViewController, BasicChildInfo {
      private var userText: String? = nil
    
      func userEnteredChildInfo(data: String) {
        userText.text = data
      }
    
      override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
          if segue.identifier == "DetailsViewSegue" {
            (segue.destination as! DetailsViewController).childDelegate = self
          }
          else if  segue.identifier == "PrintSegue" {
            (segue.destination as! PrintViewController).userText = self.userText
          }
      }