15

I have been looking for an answer for this, but have only found answers for segues.

I have viewController1 with a button that segues to viewController2. There is no code for this, I set it up through Interface builder. On viewController2 I have a button that dismisses itself with

 self.dismissViewControllerAnimated(true, completion, nil)

I want to pass a string from viewController2 back to viewController1 when the view is dismissed. How do I go about doing this? Also, I am using swift.

Thanks in advance!

Dharmesh Kheni
  • 71,228
  • 33
  • 160
  • 165
user3724253
  • 237
  • 2
  • 4
  • 7

3 Answers3

43

There are two common patterns, both of which eliminate the need for viewController2 to know explicitly about viewController1 (which is great for maintainability):

  1. Create a delegate protocol for your for viewController2 and set viewController1 as the delegate. Whenever you want to send data back to viewController1, have viewController2 send the "delegate" the data

  2. Setup a closure as a property that allows passing the data. viewController1 would implement that closure on viewController2 when displaying viewController2. Whenever viewController2 has data to pass back, it would call the closure. I feel that this method is more "swift" like.

Here is some example code for #2:

class ViewController2 : UIViewController {
    var onDataAvailable : ((data: String) -> ())?

    func sendData(data: String) {
        // Whenever you want to send data back to viewController1, check
        // if the closure is implemented and then call it if it is
        self.onDataAvailable?(data: data)
    }
}

class ViewController1 : UIViewController {
   func doSomethingWithData(data: String) {
        // Do something with data
    }
    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
        // When preparing for the segue, have viewController1 provide a closure for
        // onDataAvailable
        if let viewController = segue.destinationViewController as? ViewController2 {
            viewController.onDataAvailable = {[weak self]
                (data) in
                if let weakSelf = self {
                    weakSelf.doSomethingWithData(data)
                }
            }
        }
    }
}
drewag
  • 93,393
  • 28
  • 139
  • 128
  • This is great! Kind of like a lightweight, internal protocol. You can use optional chaining to call the closure without the `if let...` : `self.onDataAvailable?(data: data)` – Nate Cook Jun 20 '14 at 01:32
  • @NateCook thanks, good call on the optional chaining. I updated my answer with that. – drewag Jun 20 '14 at 01:36
  • Is onDataAvailable a closure. If so, how do I store my string in it. Sorry, I am very new to programming. – user3724253 Jun 20 '14 at 01:41
  • 1
    @user3724253 Yes it is a member variable of `ViewController2` that is a closure. You don't "store" your string in it, you call it (like a function) with your string when you want ViewController1 to get the data. I gave you an example of this with the method `sendData` in `ViewController2` – drewag Jun 20 '14 at 01:43
  • Thank you so much!! This did it! Literally a life saver! – user3724253 Jun 20 '14 at 02:06
  • 1
    @user3724253 You can access "self" in a closure, but because of [memory management issues](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-XID_61), you have to use it as a weak reference which makes things a little bit more complicated. I updated my example to show you. – drewag Jun 20 '14 at 02:07
  • @drewag according to the swift book, you should use `weak` when the reference can become nil and `unowned` if it will not become nil. If you are using a tabbarcontroller and viewcontroller1 is one of the initial tab view controllers, would that be `unowned`? (In other words, do the initial view controllers on tab based apps ever become nil?) – dewyze Jan 05 '15 at 06:01
  • 1
    @dewyze, whether or not `onDataAvailable` will be called on ViewController2 after ViewController1 is destroyed is dependent on how the rest of the code is written. There is not enough code/information here to say for sure, especially because we do not know how these view controllers are being created and if they are stored anywhere else. We also don't know when `sendData` is being called. Weak references are always safer so defaulting to weak is always a good idea. – drewag Jan 05 '15 at 06:09
  • @drewag Any chance you will update the example to use multiple variables of various types? – Jed Grant May 26 '15 at 00:23
  • great answer i have done up vote for it from my side – Nischal Hada Mar 03 '16 at 10:30
1

I used the code from the first answer in a transition between controllers WITHOUT prepareForSegue and worked for me as well. Here's the sample code.

The First View Controller:

@IBAction func dpAgendaClick(sender:UIBarButtonItem) {
    ///instantiating view controller with identifier
    if let datePickerViewController = storyboard?.instantiateViewControllerWithIdentifier("DatePickerViewController") 
        as? DatePickerViewController {
            ///bring instantiated view controller to front
            self.presentViewController(datePickerViewController, animated: true, completion: nil)
            ///wrapping the data returned
            datePickerViewController.onDataFiltroAvailable = {[weak self]
                (dataFiltro) in
                if let weakSelf = self {
                    ///use dataFiltro here
                }
}

The second View Controller:

var onDataFiltroAvailable: ((dataFiltro: String) -> ())?
///private var
var dataFiltro: String = ""
///the returning data is obtained on the datePickerChanged event
@IBAction func datePickerChanged(sender: UIDatePicker) {
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle
    dateFormatter.dateFormat = "yyyy-MM-dd"
    dataFiltro = dateFormatter.stringFromDate(datePicker.date)
}

///dismiss the controller on button click
@IBAction func dpOkClick(sender: UIButton) {
    ///"returning" the data
    self.onDataFiltroAvailable?(dataFiltro: dataFiltro)
    dismissViewControllerAnimated(true, completion: nil) 
}
Pablo
  • 361
  • 3
  • 6
0

(Swift 2.1, Xcode 7, iOS9) If you don't want it to be tightly coupled only between 2 ViewControllers, 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
    }
     }

Common Practise is:

  1. Pass data forward -> Use PrepareForSegue
  2. Pass data backward to the previous View Controller-> Protocol and Delegation
  3. Pass data across multiple View Controllers -> Notifications : Post and Observe(observe in all the View controllers where you are using the object details)
Naishta
  • 11,885
  • 4
  • 72
  • 54