Passing Data With Unwind Segue

Passing data with unwind segue

Øyvind Hauge beat me to the same solution method, but as I had already started with a more detailed answer, I'll add it as well.


Let's say your two view controllers are named as follows:

  • Master/entry point: ViewController (vcA)
  • Secondary view: ViewControllerB (vcB)

You set up the segue from (vcA) -> (vcB) as you have done in you example

/* in ViewController.swift */   

// ...

// segue ViewController -> ViewControllerB
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!)
{
if segue.identifier == "viewNext" {
let viewControllerB = segue.destinationViewController as! ViewControllerB
viewControllerB.dataPassed = labelOne.text
}
}

The somewhat tricky step next is that, using this method, the segue used for passing data back from (vcB) to (vcA) is also added to the source of (vcA), as an @IBAction method (rather than, as could possibly be expected, added to the source of (vcB)).

/* in ViewController.swift */   

// ...

// segue ViewControllerB -> ViewController
@IBAction func unwindToThisView(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? ViewControllerB {
dataRecieved = sourceViewController.dataPassed
}
}

You thereafter connect say, a button in (vcB) to this unwind action in (vcA) via the manual Exit segue in (vcB):

Sample Image

Below follows a complete example of passing text from (vcA) to (vcB); (possibly) modifying that text via an UITextField, finally returning the (possibly) modified text to (vcA).


(vcA) source:

/* ViewController.swift: Initial view controller */
import UIKit

class ViewController: UIViewController {

var dataRecieved: String? {
willSet {
labelOne.text = newValue
}
}
@IBOutlet weak var labelOne: UILabel!

@IBAction func buttonOne(sender: UIButton) {
performSegueWithIdentifier("viewNext", sender: self)
}

// set default labelOne text
override func viewDidLoad() {
super.viewDidLoad()

labelOne.text = "Default passed data"
}

// segue ViewController -> ViewControllerB
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!)
{
if segue.identifier == "viewNext" {
let viewControllerB = segue.destinationViewController as! ViewControllerB
viewControllerB.dataPassed = labelOne.text
}
}

// segue ViewControllerB -> ViewController
@IBAction func unwindToThisView(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? ViewControllerB {
dataRecieved = sourceViewController.dataPassed
}
}
}

(vcB) source (note that the UITextFieldDelegate delegate here is only used for "locally" mutating the value of the dataPassed property, which will be returned to (vcA) and assigned to dataRecieved property of the latter)

/* ViewControllerB.swift */
import UIKit

class ViewControllerB: UIViewController, UITextFieldDelegate {

var dataPassed : String?
@IBOutlet weak var textField: UITextField!

// set default textField text to the data passed from previous view.
override func viewDidLoad() {
super.viewDidLoad()

textField.text = dataPassed

// Handle the user input in the text field through delegate callbacks
textField.delegate = self
}


// UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
// User finished typing (hit return): hide the keyboard.
textField.resignFirstResponder()
return true
}

func textFieldDidEndEditing(textField: UITextField) {
dataPassed = textField.text
}
}

Example execution:

Sample Image

How do I use an unwind Segue to pass data back from the second view controller to the first view controller?

You almost have it. You have the unwind segue method, you just aren't using it. You can access the second view controller via the sourceViewController property of the segue that is passed to your unwind method:

-(IBAction)goBackToScene1:(UIStoryboardSegue *)segue
{
//Hello, Adnan, you are 6'3 which is 191cm
self.displayField.hidden = NO;
SecondSceneViewController *source = (SecondSceneViewController *)segue.sourceViewController;
self.heightInches = source.heightField.text
self.displayField.text = [NSString stringWithFormat:@"Hello, %@, you are %@ inches", self.nameField.text, self.heightInches]; //EXAMPLE OUTPUT
}

Swift - Unwind Segue Pass Data Confusion

It seems like the data is not getting sent or its getting sent after viewDidLoad() so the label I'm trying to set isn't getting updated.

In an unwind segue you are returning to an already created viewController, so viewDidLoad happened ages ago before you segued to the other viewController.


If you're using segues, you should not be mucking with the array of viewControllers in the navigationController or calling dismiss. The unwind segue will do all of that. Just get the destination in prepare(for:sender:) and set the data:

if segue.identifier == "unwindToViewController1" {

let nbaRotoHome = segue.destination as! NBARotoHome

nbaRotoHome.player = "new player name"
}

or in your prepareForUnwind get the source and read the data:

In this line you are missing .source. Change:

if let sourceViewController = segue as? BuyStats

to:

if let sourceViewController = segue.source as? BuyStats

Pass data through unwind segue

If I understand your question correctly, you should do something like this:

class MyPickerController : UIViewController {
@IBOutlet weak var myLabel: UILabel!

var pickerData:[String]

func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
myLabel.text = pickerData[row]
performSegueWithIdentifier( "mySegueName", sender: self )
}

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let myController = segue.destinationController as? MyDestinationViewController {
myController.text = myLabel.text
}
}

That will pass the label text to the MyDestinationViewController instance.
Note that I don't pass the UILabel object, but the text.
You can not place that UILabel object in the destination controller interface, but you can use the text an set another label with it, for example.

Delegate vs Unwind Segue for Passing Data to Parent Scene

I realize now that this isn't truly an answerable question other than to say that neither approach is wrong - they both have their pros and cons. After having tackled both for a week and done more reading on the subject I can at least quantify why you might want to use either an unwind segue or delegates for working between view controllers.


Coupling

Both models are roughly equally (loosely) coupled. Under the hood, an unwind segue is just a delegate where iOS has done the work of wiring it up for you. For delegates, the parent knows about and conforms to the child protocol. For unwind segues, the parent has to be wired to the child on the storyboard for unwind and needs to know the properties of the child to extract the return data. However, if you're new to delegates and just want to get some data out of a child view, unwind segues are probably less intimidating than using protocols with delegates.


Flexibility

Unwind segues are only a good choice if the sole purpose of the child-to-parent interaction is to return data. There does not appear to be a way to cancel an unwind segue in progress. So if the parent has to do any data validation, or if the child needs more than one interaction with the parent, the only way to do this is to have a delegate where multiple methods can be called back to the parent.


Maintainability

If the type or other aspects of the data being returned changes, it will be easier to update the unwind segue as all you have to do is to update the code in your unwind segue to look at the new properties. For the protocol/delegate approach, you will have to update the protocol in the child and the implementation in the parent. However, the simplicity of the unwind segue comes at the cost that you may easily miss places in parent view controllers that require updating because you don't have the compiler checking your contract (the protocol).


The Winner

There isn't one. Which way you go depends on your data needs, comfort level with protocols (they look more intimidating on first glance than they should), complexity of your application, and long term maintenance needs.

For my purposes, I wound up using delegates because my child had to make more than one call back to the parent in some cases. However, in a few instances where I had many pieces of data to pass back, I adopted what I learned from the unwind segue and simply used properties in the child from which the parent could extract the needed information. I also used this as a convenient path for the parent to provide error information to the child. I don't mix and match unwind segues with delegates in the program for consistency with a programming partner, but there's no reason you couldn't do that if you wanted to.

How to pass data in conditional unwind segue?

Like Schemetrical said, using a delegate is an easy way to access the methods in your MainViewController.

Since you tagged this as Swift, I'll also give you a small example of a delegate in Swift.

First you create a protocol:

protocol NameOfDelegate: class {     // ":class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() -> String // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}

In your MainViewController you have to add:

class MainViewController: UIViewController, NameOfDelegate {

// your code

@IBAction func button(sender: UIButton) {
performSegueWithIdentifier("toOtherViewSegue", sender: self)
}

fun someFunction() -> String {
// access the other methods and return it
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toOtherViewSegue" {
let destination = segue.destinationViewController as! OtherViewController
destination.delegate = self
}
}
}

And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two view controllers so they can talk to each other.

class OtherViewController: UIViewController {

weak var delegate: NameOfDelegate?

@IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}

I assumed you used a segue to access your other ViewController since you mentioned it in your post. This way, you can just "talk" to your MainViewController.

EDIT:

As for the unwind. This also can be done through a segue.

  1. add: @IBAction func unwindToConfigMenu(sender: UIStoryboardSegue) { } to your MainViewController.
  2. In your storyboard there are 3 icons at the top of your OtherViewController. Click on the round yellow with a square inside to make sure the ViewController is selected and not some elements inside.
  3. Control drag (or right mouse drag) from the same round yellow with a square inside to the most right red square icon. Doing so pops up a menu where you can select the unwind segue.
  4. Click on the new segue you just created. Give it an identifier like "backToMain"
  5. Add something similar as the code below to OtherViewController

It appears i can't post any code anymore? :o will add it later.

Is it possible to use an unwind segue from a single modal view controller back to one of multiple instances of the same source view controller?

Apple has a comprehensive technical note on how unwind segues work and how the destination view controller is determined, but, in summary, the process examines the view controller navigation hierarchy to find the first view controller that can handle the unwind segue and is willing to do so.

In your case, this would be the MainVC instance that presented the ModalVC that is unwinding. The unwind segue cannot be handled by a view controller instance that is not in the navigation hierarchy (e.g. An instance of MainVC that did not present the ModalVC)

Passing data with Unwind segue UITextField

The error is saying that you are tying to set imageButtonURL on an instance of Reports, not on Shop which is what you think your destination view controller is. It appears that your unwind is going to the wrong controller. You must have hooked up the unwind segue incorrectly. You say that you have 2 views (view controllers actually), but you must also have a Reports class in your app.



Related Topics



Leave a reply



Submit