10

I have two view controllers, One and Two. I go from VC One to VC Two. On VC Two, I select some data that I store in an array. When I press the "Back" button on the navigation bar, I would like to send that array back to VC One.

What's the best way to do this using Swift & Storyboards?

Thanks!

user1797508
  • 292
  • 5
  • 16
  • Normally I would say unwind segues but I do not believe thats available yet in Xcode 6 with Swift. – Peter Foti Jun 19 '14 at 04:03
  • @PeterFoti You'd be wrong. All of the normal mechanisms for doing this still apply. The preferred mechanism is to either use a delegate or completion block in the child view controller. If the OP can look around and take a poke at one or the other of those with a more specific question, I'm sure he can get more help. – David Berry Jun 19 '14 at 05:13
  • @David interesting, according to the Xcode 6 release notes `Unwind segue actions declared in Swift classes are not recognized by Interface Builder.
` Yes, their is a workaround but its not baked in yet. And doing this in an unwind segue would be my preferred method using Storyboards. – Peter Foti Jun 19 '14 at 13:18
  • @peterfoti Actually, I missed the part about unwind segue's since I normally view those as more complicated and intended primarily for unwinding complicated stacks. For just simple purposes like the OP is asking, callbacks and or delegates seem to be the "right" way. – David Berry Jun 19 '14 at 16:10
  • @David I would read up on unwind segues again. They're dead simple, and a much easier way of transferring simple data across VC's. – Peter Foti Jun 19 '14 at 16:48
  • @PeterFoti Unwind's aren't triggered on back button presses, correct? – David Berry Jun 19 '14 at 16:55
  • Thankseveryone for your input! – user1797508 Jun 19 '14 at 21:13

3 Answers3

26

If you were presenting a modal view with Done and Cancel buttons (sort of like a picker), grabbing the value during an unwind segue method would probably be the easiest.

Given that you want to use the navigation controller's native Back button, the best practice would probably be to implement a protocol that VC One can conform to, and then update VC One as soon as the data on VC Two is selected. Something like:

In VCTwo.swift:

protocol VCTwoDelegate {
    func updateData(data: String)
}

class VCTwo : UIViewController {
    var delegate: VCTwoDelegate?
    ...
    @IBAction func choiceMade(sender: AnyObject) {
        // do the things
        self.delegate?.updateData(self.data)
    }
    ...
}

and in VCOne.swift:

class VCOne: ViewController {
    ...
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "VCTwoSegue" {
            (segue.destinationViewController as VCTwo).delegate = self
        }
    }
    ...
}

extension VCOne: VCTwoDelegate {
    func updateData(data: String) {
        self.internalData = data
    }
}
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • This is great, thanks for your help! Just one comment - it's func updateData(data: String) Rather than func updateData(data: String[]) if you're returning just a string – user1797508 Jun 19 '14 at 21:12
  • Oh sure! I read your Q as saying the *selection* is stored in an array, not that you were selecting a value from an array - makes sense. I'll update my answer. – Nate Cook Jun 20 '14 at 00:55
  • Thanks Nate! Your help was a saver! – user1797508 Jun 20 '14 at 01:28
  • There's a nearly identical question with a great alternate answer: http://stackoverflow.com/a/24318588/59541 - do take a look. – Nate Cook Jun 20 '14 at 01:33
  • @NateCook great! Thanks :) – iRiziya Aug 17 '15 at 09:22
  • In Swift 3 the prepare for segue function call has changed to: override func prepare(for segue: UIStoryboardSegue, sender: Any?) { – user1797508 Feb 05 '17 at 13:16
6

You can also use the Notification Design Pattern (Post & Observe), which is mainly used to pass on the same object/information from one VC to multiple View Controllers.

For your scenario : In VC2.swift :

@IBAction func BackBtn(sender: UIButton) {  
  NSNotificationCenter.defaultCenter().postNotificationName("ThisIsTheMessage", object: nil, userInfo:["ObjectBeingSent":yourObject])

}

And in VC1.swift :

override func viewDidLoad() {
        super.viewDidLoad()
        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("yourFunction:"), name: "ThisIsTheMessage", object: nil)
    }



func yourFunction(theNotification : NSNotification) {

           if let extractInfo = theNotification.userInfo {
     //code to use the object sent from VC2, by extracting the object details
    }
     }
Naishta
  • 11,885
  • 4
  • 72
  • 54
2

Here is my solution in Swift 3

1) Create a protocol inside the SecondController.swift file. We preferably create the protocol from where we will be getting data from.

protocol Protocol {
        func passingDataBack(withString: String)
}   

2) Create a variable of type Protocol

var proto: Protocol!

3) Switch to the ViewController.swift file and inherit the Protocol we made from the SecondController.swift file.

class ViewController: UIViewController, Protocol {

}   

4) We then want to conform to the Protocol we made by creating the function we made

    func passingDataBack(withString: String) {
//        withString will return the value that has been passed from our SecondController class
        self.title = withString

    }

5) Use the prepareForSegue method and segue to the SecondController class

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let vc = segue.destination as? SecondController
        vc?.proto = self //This line will instantiate the protocol to our ViewController class
    }

6) Go back to our SecondController.swift file and use the didSelectRow method and pass our data

 override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    proto.passingDataBack(withString: items[indexPath.row]) //Call the protocol and the function then pass our data.
    _ = self.navigationController?.popViewController(animated: true) //This will pop back to our previous controller.

}

* Important Things To Remember!!! *

You must set the protocol from controllerB to instantiate to controllerA when switching from controllerA to controllerB

In our example, we moved from ViewController to SecondController. We instantiate our protocol from our SecondController by doing

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let vc = segue.destination as? SecondController
    vc?.proto = self //This line will instantiate the protocol to our ViewController class
}

If you do not do this, you will get a Thread 001 error on this line

proto.passingDataBack(withString: items[indexPath.row]) //Call the protocol and the function then pass our data.

Source code Github

Cyril
  • 2,783
  • 1
  • 24
  • 35