Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS

Passing data from modal view controller to parent

How do I pass data entered on a modal view to the parent? Example, when I press the 'save' button, I would like for the modal view to go away but the information be stored in the parent view controller.

I am using Swift as the programming language.

2 Answers

Andrew Shook
Andrew Shook
31,709 Points

So, traditionally, you would use the delegate pattern for this. Basically, you would pass the original view controller to the modal view controller before you segue to the modal view. Then you in the modal view controller, you would set the data on the original view controller when you click the "save" button.

class ViewController : UIViewController {
    var userName : String = " ";

    lazy var showModalButton : UIButton = {
        let button = UIButton();
        button.setTitle("show modal", for: .normal);
        button.backgroundColor = UIColor.blue;
        // add 'click event' to button
        button.addTarget(self, action: #selector(showModal), for: .touchUpInside);
        return button;
   }();

    override func viewDidLoad() {
        super.viewDidLoad()
        // add button to view controller's view.
        self.view.addSubview(self.showModalButton);
        // center the button vertically and horizontally.
        self.view.addConstraint(NSLayoutConstraint(item: self.showModalButton, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0));
        self.view.addConstraint(NSLayoutConstraint(item: self.showModalButton, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0));
    }

    // this is the function that is called when the button is tapped.
    func showModal()
    {
        let modalController = ModalController();
        // self the parent as a property on the child. Usually, the name of the property is 'delegate', 
        // but you can actually call it whatever you like. To help other's, you should add a comment
        // to the property mentioning the fact the it's a delegate.
        modalController.delegate = self;
        self. presentViewController( modalController, animated: true, completion: nil);
    }



}

class ModalViewController : UIViewController {

    lazy var saveButton : UIButton = {
        let button = UIButton();
        button.setTitle("save name", for: .normal);
        button.backgroundColor = UIColor.blue;
        // add 'click event' to button
        button.addTarget(self, action: #selector(saveName), for: .touchUpInside);
        return button;
    }();

    self.delegate : ViewController?;

    let nameField = UITextInput();

    override func viewDidLoad() {
        super.viewDidLoad()
        // add button to view controller's view.
        self.view.addSubview(self.saveButton);
        // center the button horizontally and place button 50 pixel below the views center point.
        self.view.addConstraint(NSLayoutConstraint(item: self.saveButton, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 50));
        self.view.addConstraint(NSLayoutConstraint(item: self.saveButton, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0));
        // add text field to view controller's view.
        self.view.addSubview(self.nameField);
        // center the text field horizontally and vertically.
        self.view.addConstraint(NSLayoutConstraint(item: self.nameField, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0));
        self.view.addConstraint(NSLayoutConstraint(item: self.nameField, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0));
    }

    // this is the function that is called when the button is tapped.
    func saveName()
    {
        // pass the data from the child's text field to the parent by setting is on a property of the parent.
        self.delegate?.userName = self.nameField.text as? String;
        self.dismiss(animated: true, completion: nil);
    }



}

Would there be any IBActions or IBOutlets with this?

Jhoan Arango
Jhoan Arango
14,575 Points

Hello Joshua:

There are different ways you can get this done. But I find segues to be the easiest to use and are also recommended by Apple.

On this particular example, you want to use the unwind segue.

Step One: On your main controller, you want to have the unwind segue.

class ViewController: UIViewController {

    // MARK: - Property   
    @IBOutlet weak var textView: UITextView! 

    // MARK: - Life Cycle   
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK: - Navigation

   // This is your unwind Segue, and it must be a @IBAction
    @IBAction func unwindToViewController(segue: UIStoryboardSegue) {
        let source = segue.source as? SecondViewController // This is the source
        textView.text = source?.textField.text // Here we are getting the information and setting it to the textView
    }

}

Step Two: On your second view controller, is where you have the information or data that you want to send back to the main controller. In this example is just a String. You don't even have to have an outlet for your button.

class SecondViewController: UIViewController {

    // MARK: - Property
    @IBOutlet weak var textField: UITextField!

    // MARK: - Life Cycle   
    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

Then Hooking up the segue is as simple as dragging and dropping. From the "Button" you press control and drag to the top of the view controller where it says "exit".

Sample

Once you drag, you will find an unwind segue selection, which it should be "unwindToViewController".

Sample 2

And that's it, that should do the trick.

This is how the storyboard looks like in my example:

Sample 3

And this is how it works!

app

Adding text

app

Then the result of passing data

app

Best of luck, and if you do not understand or need more help let me know. Also, let me know if this solution worked for your app.

If I would like the data to be sent to a label instead of a text field, then on the first controller I would replace textview: UITextView! with label: UILabel!?

Jhoan Arango
Jhoan Arango
14,575 Points

Yes, all you do is create an outlet for your label, and then pass the data to it.

@IBOutlet weak var myLabel: UILabel!

myLabel.text = source?.textField.text

Good luck

I am receiving an error. EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). This error appears as a red error over the following line of code:

myLabel.text = source?.textField.text

The output at the bottom of Xcode reads: "fatal error: unexpectedly found nil while unwrapping an Optional value". This is odd because I entered a string into the textField of the second VC. There should not be a nil to be found because there was a string.

Jhoan Arango
Jhoan Arango
14,575 Points

Joshua, you would have to show me a bit more about your project or your code to be able to find what's wrong. If you want you can add your project in GitHub and I will take a look at it.

I created a project on GitHub. Link is below. I added the two files that I am working with. If I need to add anything else to GitHub, let me know. This is the first time that I created a project with GitHub. Thanks!

https://github.com/thejdah/modalpassdatatoparent

Jhoan Arango
Jhoan Arango
14,575 Points

Hey, Joshua should upload the entire project. I need to see if you connected everything and with just these files I can't see that.