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 trialJoshua Hardy
17,317 PointsPassing 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
31,709 PointsSo, 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);
}
}
Jhoan Arango
14,575 PointsHello 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".
Once you drag, you will find an unwind segue selection, which it should be "unwindToViewController".
And that's it, that should do the trick.
This is how the storyboard looks like in my example:
And this is how it works!
Adding text
Then the result of passing data
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.
Joshua Hardy
17,317 PointsIf 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
14,575 PointsYes, 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
Joshua Hardy
17,317 PointsI 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
14,575 PointsJoshua, 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.
Joshua Hardy
17,317 PointsI 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!
Jhoan Arango
14,575 PointsHey, Joshua should upload the entire project. I need to see if you connected everything and with just these files I can't see that.
Joshua Hardy
17,317 PointsJoshua Hardy
17,317 PointsWould there be any IBActions or IBOutlets with this?