Search code examples
iosswiftswift3uiviewcontrollerclosures

What's the easiest way to pass data between two simultaneously displayed view controllers?


I'm getting in a muddle trying to pass data between two viewcontrollers that are displayed on one screen via container views.

Minimal example below - top view (TopVC) has a textInput field. When I press the button, I want the label on the bottom view (BottomVC) to display the inputted text. Moreover, I want it to pass back a message to the TopVC and update the topVC label with the message "Successfully contacted bottom VC"

Storyboard set up

Screen Shot

I have no idea now to reference the view controllers from each other basically.

class TopViewController: UIViewController {

    @IBOutlet weak var textInput: UITextField!
    @IBOutlet weak var textOutput: UILabel!

    @IBAction func go(_ sender: UIButton) {
        // ??????????? 
    }

    override func viewDidLoad() {  
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.blue
    }

    func callMeBaby(greeting: String) {
        textOutput.text = greeting
    }
}

In the ????? placeholder, I want to put something that basically works like BottomVC.test(textInput.text, callmebaby) - but obviously I need to put in some extra code to 'introduce' the two ViewControllers, and I'm not sure what to do.

class BottomViewController: UIViewController {

    @IBOutlet weak var textLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.yellow
    }

    func test(input: String, completion: (String) -> Void) {
        textLabel.text = input
        completion("Successfully contacted bottom VC")
    }
}

Solution

  • Creating Delegates

    Start with creating delegates for both of your container ViewControllers. Don't forget to add : class. If you didn't do it, you wouldn't be able to create weak delegate variable:

    protocol TopViewControllerDelegate: class {
        func sendMessage(_ string: String)
    }
    protocol BottomViewControllerDelegate: class {
        func sendMessage(_ string: String)
    }
    

    Now for every container ViewController create weak delegate variable

    class TopViewController: UIViewController {
        weak var delegate: TopViewControllerDelegate?
        ...
    }
    
    class BottomViewController: UIViewController {
        weak var delegate: BottomViewControllerDelegate?
        ...
    }
    

    then for TopVC implement Bottom's delegate and for BottomVC Top's.

    extension TopViewController: BottomViewControllerDelegate {
        func sendMessage(_ string: String) {
            // do something
        }
    }
    extension BottomViewController: TopViewControllerDelegate {
        func sendMessage(_ string: String) {
            // do something
        }
    }
    

    Assigning Delegates

    Your segues between main ViewController and containers should have their own identifiers: EmbedTop, EmbedBottom.

    So in your WrapperViewController create variable for your Top and Bottom ViewController and override method prepare(for:sender:) and inside assign these variables

    class WrapperViewController: UIViewController {
    
        var topVC: TopViewController?
        var bottomVC: BottomViewController?
    
        ...
    
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if segue.identifier == "EmbedTop" {
                topVC = segue.destination as! TopViewController
            } else if segue.identifier == "EmbedBottom" {
                bottomVC = segue.destination as! BottomViewController
            }
        }
    
    }
    

    finally in viewDidAppear set delegate of TopVC's as BottomVC and of BottomVC's as TopVC

    override func viewDidAppear(_ animated: Bool) {
        topVC.delegate = bottomVC
        bottomVC.delegate = topVC
    }
    

    Now your two ViewControllers can speak with each other! :-)


    Example:

    class BottomViewController: UIViewController {
        ...
        func speakToTop() {
            delegate?.sendMessage("Hi Top!")
        }
    }