Passing data with unwind segue
Asked Answered
H

3

36

I created two view controllers. I created a segue from the first to the second to pass data. Now I want to pass data from the second view controller to the first one. I went through many similar questions and I'm not able to implement those as I lack the knowledge on how unwinding works.

ViewController.swift

class ViewController: UIViewController
{   
    var dataRecieved: String?
    @IBOutlet weak var labelOne: UILabel!
    @IBAction func buttonOne(sender: UIButton)
    {
        performSegueWithIdentifier("viewNext", sender: self)
    }
    override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!)
    {

        var svc: viewControllerB = segue.destinationViewController as! viewControllerB
        svc.dataPassed = labelOne.text
    }
}

This will pass the data to dataPassed in view controller "viewControllerB". Say, now I want to pass some data from viewControllerB to dataRecieved in ViewController. How can I do this with only unwind segue and not by using delegate. I'm quite new to swift, would appreciate a detailed explanation.

Hols answered 10/2, 2016 at 11:19 Comment(3)
Simply implement prepareForSegue in your unwinding view controller and access the destinationViewController which will be the view controller you are unwinding to. You will probably want to give the unwind segue and identifier in your storyboardGlaudia
It gives an error "has no segue with identifier 'btnSubmitSegue'' ". I have added the segue identifier in the view controller. I'm using only one segue to connect both the view controllers. I believe unwind segue just winds back to it's previous view controller without any additional segues? Can you please explain in detail? Some code would be appreciated :)Hols
You create the unwind segue as per usual - drag to the exit icon in the scene. Now in the object inspector on the left you will see the unwind segue listed below the view controller, first responder and exit icons. You can click on the unwind segue and give it an identifier in the inspector on the right.Glaudia
C
72

Ø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):

enter image description here

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:

enter image description here

Contrecoup answered 10/2, 2016 at 12:4 Comment(8)
Thank you very much for the detailed explanation! That helped me understand better :)Hols
Maybe its right in front of me but what is 'newValue' in 'willSet { labelOne.text = newValue }'Cedric
@DarkhorseFantasySports no worries: it's a default name for the new value to be set: "If you implement a willSet observer, it’s passed the new property value as a constant parameter. You can specify a name for this parameter as part of your willSet implementation. If you don’t write the parameter name and parentheses within your implementation, the parameter is made available with a default parameter name of newValue." - From the Swift Language Guide - Properties.Contrecoup
I guess this is the best possible answer and being new to iOS got to learn some thing interesting as i was knowing delegation was the only way to pass data back to view controller, and also got to know about this "newValue" thing! Thanks for this answer.Catcall
In Swift 4.0 it seems the name has changed.sender.sourceViewController -> sender.source.Anson
Awesome, but this don't like me from Apple. Some thinks are so tricky and out of context. Where are this documented ?Nelda
@Wo_0NDeRᵀᴹ I wrote this answer 7 years ago so unfortunately I don't remember if I found this out via experimentation or via documentation (not whether this is still a valid answer for modern Swift - I don't do iOS development anymore).Contrecoup
@Contrecoup Excellent explanation. Very clearly stated. Thank you very much :)Deportation
D
15

This is how I would do it:

  1. Create an outlet in view controller 1, like this:

    @IBAction func unwindToViewController1(segue: UIStoryboardSegue) {
    
       let foo = segue.sourceViewController.foo
    
       // TODO: Use foo in view controller 1
    }
    
  2. Connect view controller 2 (the vc you are unwinding from) like shown below. Drag from the yellow circle in vc2 to 'Exit'. The IBAction from view controller 1 should pop up. Select it. enter image description here

  3. Now, whenever you unwind from view controller 2, the unwindToViewController1: method in view controller 1 will get called.

  4. This is where you'll retrieve the property you want from view controller 2. Note that you need to cast the segue.sourceViewController to your custom view controller subclass in order to get the right property.

Digestion answered 10/2, 2016 at 11:32 Comment(1)
Thanks! That answered my question :)Hols
P
4

If your app supports iOS 9+ you can pass data almost the same as prepareForSegue, use UIStoryboardUnwindSegueSource that has a sender property which is exactly the same as the the sender property in prepare(for segue: UIStoryboardSegue, sender: Any?).

How to use it:

  1. Create an unwindTo method.

Note: Connecting the unwindTo method is the same as @Øyvind Hauge and @dfri explained in their answers.

  1. Inside the view controller you want to unwind to, override the method canPerformUnwindSegueAction(_:from:withSender:)
  2. Inside this method, check if the type fromViewController is the type you were coming from
  3. If it is, cast the sender property to the type you sent and return true
  4. Else, return false

Code snip (Swift 4.0):

@IBAction func unwindToMyFirstViewController(segue: UIStoryboardSegue) {}

override func canPerformUnwindSegueAction(_ action: Selector, from fromViewController: UIViewController, withSender sender: Any) -> Bool {
    if fromViewController is MyCustomViewController,
        let customType = sender as? MyCustomType {
        return true
    }
    return false
}
Pyrrhuloxia answered 22/2, 2018 at 12:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.