63

I am trying to pass data BACK TO previous viewController.

Does anyone know how to pass data back from ViewController B to ViewController A? So I want a string to go 'from' BIDAddTypeOfDealViewController to BIDDCCreateViewController. A user edits viewController B and I want that edited data back in ViewController A where I then use it.

I am using the 'passing data back' section of this answer. How mine differs: Point 3 and 6 just mentions when views are popped so I have put that code in viewWillDisappear. I think that is correct? Also on Point 6 I did not initialise with nib as that is old. I'm using storyboards. And I did not add that last line as I do not believe I would have to push it. Pressing a button on my storyboard already takes me forward.

I think the problem may arise in BIDDCCreateViewController, I have the method but I cannot run it. To run a method it should go [self method]. I am unable to do that. Well that is just what I am guessing.

It compiles and runs fine just nothing is logged, so I don't know if it works.

UPDATE: I am unable to get the 'sendDataToA' method to execute.

#import <UIKit/UIKit.h>
#import "BIDAddTypeOfDealViewController.h"

 @interface BIDDCCreateViewController : UIViewController
 @property (strong, nonatomic) NSString *placeId;
- (IBAction)gotoBViewController:(id)sender;
@end


#import "BIDDCCreateViewController.h"
#import "BIDAddTypeOfDealViewController.h"

@implementation BIDDCCreateViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"SUCCESSFULLY PASSED PLACE ID: %@", self.placeId);
}

-(void)sendDataToA:(NSString *)myStringData
{

    NSLog(@"Inside sendDataToA");
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Your string Data Showing" message:myStringData delegate:self cancelButtonTitle:@"Ok " otherButtonTitles:nil];
    [alert show];
}

- (IBAction)gotoBViewController:(id)sender {
    NSLog(@"pressed");
    BIDAddTypeOfDealViewController *bidAddType = [[BIDAddTypeOfDealViewController alloc]init];
    bidAddType.delegate = self;

}
@end


@protocol senddataProtocol <NSObject>
-(void)sendDataToA:(NSString *)myStringData;
@end

#import <UIKit/UIKit.h>
@interface BIDAddTypeOfDealViewController : UIViewController <UITextFieldDelegate>//Using this delegate for data a user inputs
@property(nonatomic,assign)id delegate;
//other textfield outlets not relevant
- (IBAction)chooseDiscountDeal:(id)sender;
@end

#import "BIDAddTypeOfDealViewController.h"

@interface BIDAddTypeOfDealViewController ()

@end

@implementation BIDAddTypeOfDealViewController
@synthesize delegate;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
}

-(void)viewWillDisappear:(BOOL)animated
{
    [delegate sendDataToA:@"Apple"];
}
@end
Community
  • 1
  • 1
Anthony
  • 879
  • 3
  • 11
  • 17

9 Answers9

101

You can use a delegate. So in your ViewController B you need to create a protocol that sends data back to your ViewController A. Your ViewController A would become a delegate of ViewController B.

If you are new to objective C, please look at What is Delegate.

Create protocol in ViewControllerB.h :

#import <UIKit/UIKit.h>

@protocol senddataProtocol <NSObject>

-(void)sendDataToA:(NSArray *)array; //I am thinking my data is NSArray, you can use another object for store your information. 

@end

@interface ViewControllerB : UIViewController

@property(nonatomic,assign)id delegate;

ViewControllerB.m

@synthesize delegate;
-(void)viewWillDisappear:(BOOL)animated
{
     [delegate sendDataToA:yourdata];

}

in your ViewControllerA : when you go to ViewControllerB

ViewControllerA *acontollerobject=[[ViewControllerA alloc] initWithNibName:@"ViewControllerA" bundle:nil];
acontollerobject.delegate=self; // protocol listener
[self.navigationController pushViewController:acontollerobject animated:YES];

and define your function:

-(void)sendDataToA:(NSArray *)array
{
   // data will come here inside of ViewControllerA
}

Edited :

You can See this example : How you can Pass data back to previous viewcontroller: Tutorial link

Community
  • 1
  • 1
Erhan Demirci
  • 4,173
  • 4
  • 36
  • 44
  • Erhan, perhaps if you take another quick look at my current code. I dont have 'synthesize' as I do not believe this is needed with ARC. Or the 'initWithNibName' as I am using storyboard, or even the pushing of the viewController. Other than that the code is the same, but mine does not work. Do you know why? That really is the question. – Anthony Oct 13 '13 at 19:35
  • you can synthesize it or no . if you can't do it , it's not problem . you have to use like self.objecct or _objecct. İf you follow my answer really it's will help you . please read very well. – Erhan Demirci Oct 13 '13 at 20:23
  • Have you tried this code. It doesn't even compile. Just by looking at it I would of thought that. ViewControllerA doesnt have a delegate, that is in viewControllerB. So I think your trying to set something that isn't there. Perhaps if you adapt my solution so the answer relates to me directly? Thanks. – Anthony Oct 14 '13 at 06:29
  • Yes i used in my project . Really i represent pure example for pass do data . If you can't understand this example , look on google: how can i use delegate in ios : – Erhan Demirci Oct 14 '13 at 06:59
  • Hello Erhan - I dont know what googling is going to achieve. The viewController A doesn't have delegate from what I can see. And the code doesn't compile because of that. Can you see the problem? Viewcontroller B has the 'delegate' attribute. – Anthony Oct 14 '13 at 10:36
  • Hello @Anthony I edited answer. I did a tutorial for you .I hope it's can help you – Erhan Demirci Oct 14 '13 at 13:02
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/39181/discussion-between-anthony-and-erhan-demirci) – Anthony Oct 14 '13 at 13:02
  • Hi Erhan - I have updated my code using your tutorial. Please check my updated question. I am however unable to get the 'sendDataToA' method to execute. I the only difference between my code and your tutorial is 'gotoBViewController'. Mine is different as I am not using xib, I am using storyboard. So I guess there must be a problem there but I am just not to sure. Any suggestions? – Anthony Oct 14 '13 at 20:33
  • For storyboard, you need to set the delegate inside - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ SJTableViewController *vc = (SJTableViewController *)segue.destinationViewController; vc.delegate = self;} – user1872384 Apr 29 '15 at 10:38
  • 2
    This answer is wrong because it needs this in viewcontrollerA: @property (nonatomic, weak) id delegate; – James Lin Dec 31 '15 at 18:10
  • @JamesLin no, Because assigning to 'id' from incompatible type 'ViewControllerA *const __strong' !! – Anurag Sharma Jun 14 '17 at 13:29
69

A shorter and simpler method than protocol/delegate is to create a closure:

For sending a String back from ViewControllerB to ViewControllerA in my case. In ViewControllerA:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let viewControllerB = segue.destination as? ViewControllerB {
        viewControllerB.callback = { message in
            //Do what you want in here!
        }
    }
}

In ViewControllerB:

var callback : ((String) -> Void)?

@IBAction func done(sender: AnyObject) {
    callback?("Hi")
    self.dismiss(animated: true, completion: nil)
}
Heinrisch
  • 5,835
  • 4
  • 33
  • 43
  • Very helpful and straightforward! Works in first attempt itself.. Thanks! – Mamta Mar 23 '18 at 17:51
  • 1
    [Understanding memory leaks in closures](https://medium.com/@stremsdoerfer/understanding-memory-leaks-in-closures-48207214cba) – RajeshKumar R Apr 04 '18 at 11:49
  • 3
    I want to use this one too - it's fast. But I also worry about memory leaks issue in closures. To work around leaks in closure, you must not use `"self"` directly. You must first create a weak self / unowned self, and then convert it to strong self before using it inside closure. – John Pang Sep 13 '18 at 16:33
50

Swift: Sending data back using the delegate pattern

My full answer that covers passing data both ways is here. My answer explaining the delegate pattern is here.

To pass data back from the second view controller to the first view controller, you use a protocol and a delegate. This video is a very clear walk though of that process:

The following is an example based on the video (with a few modifications).

enter image description here

Create the storyboard layout in the Interface Builder. Again, to make the segue, you just Control drag from the button to the Second View Controller. Set the segue identifier to showSecondViewController. Also, don't forget to hook up the outlets and actions using the names in the following code.

First View Controller

The code for the First View Controller is

import UIKit

class FirstViewController: UIViewController, DataEnteredDelegate {
    
    @IBOutlet weak var label: UILabel!
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "showSecondViewController" {
            let secondViewController = segue.destinationViewController as! SecondViewController
            secondViewController.delegate = self
        }
    }
    
    func userDidEnterInformation(info: String) {
        label.text = info
    }
}

Note the use of our custom DataEnteredDelegate protocol.

Second View Controller and Protocol

The code for the second view controller is

import UIKit

// protocol used for sending data back
protocol DataEnteredDelegate: class {
    func userDidEnterInformation(info: String)
}

class SecondViewController: UIViewController {
    
    // making this a weak variable so that it won't create a strong reference cycle
    weak var delegate: DataEnteredDelegate? = nil
    
    @IBOutlet weak var textField: UITextField!
    
    @IBAction func sendTextBackButton(sender: UIButton) {
        
        // call this method on whichever class implements our delegate protocol
        delegate?.userDidEnterInformation(textField.text!)
        
        // go back to the previous view controller
        self.navigationController?.popViewControllerAnimated(true)
    }
}

Note that the protocol is outside of the View Controller class.

That's it. Running the app now you should be able to send data back from the second view controller to the first.

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • Why is the delegate an optional AND set to nil? Just curious. – user1259201 Apr 01 '16 at 13:28
  • 5
    The delegate is an optional so that it has the possibility of being nil. The [default initialization value of a class optional is nil](http://stackoverflow.com/q/24065442/3681880) already, but it seems to me that stating it explicitly is more clear. See [this answer](http://stackoverflow.com/a/34566876/3681880) also for more notes about using delegates. – Suragch Apr 01 '16 at 14:26
2

Edit: Use @Erhan's solution above. Not this one. This is not a good solution.

This will help. Write this in your ViewControllerB.

    // Get array of current navigation stack
    NSArray *arrayViewControllers = [self.navigationController viewControllers];

    // Get previous viewController object from it
    YOUR_VIEW_CONTROLLER_NAME *objViewController = (YOUR_VIEW_CONTROLLER_NAME *)[arrayViewControllers objectAtIndex:arrayViewControllers.count-2];

    // For safety this check is needed. whether it the class that you want or not.
    if ([objViewController isKindOfClass:[YOUR_VIEW_CONTROLLER_NAME class]])
    {
        // Access properties of YOUR_VIEW_CONTROLLER_NAME here
        objViewController.yourProperty = YOUR_VALUE;
    }
Akshit Zaveri
  • 4,166
  • 6
  • 30
  • 59
  • @TimWhiting Yes. It's not foolproof fix. Developer must handle all the cases for different ViewControllers. I will suggest you to go with Erhan's solution. When i wrote this answer (almost 17 months ago), i was newbie Now that i have almost 2 years experience, i would not prefer this method. I always use delegates. Sometimes notifications also. Let me know if you have any other questions. – Akshit Zaveri Mar 31 '15 at 03:01
2

As Erhan Demirci answered, you can use delegates. Delegates are helpful when you want to pass data to a single view controller.

NSNotificationCenter is another convenient way to transfer data between viewcontrollers/objects. This is very helpful in broadcasting data within the application.

read documentation here.

Abdullah Umer
  • 4,234
  • 5
  • 36
  • 65
1

Custom delegate is the best option to move data but you can try this also.

You can use NSUserDefaults for Moving the data any where you want.

Swift 3 Code

UserDefaults.standard.set(<Value>, forKey: <Key>) 
// To set data

UserDefaults.standard.object(forKey: <Key>) 
// To get data

You can also use NSNotification for move data.

NotificationCenter.default.post(name: Notification.Name(rawValue: "refresh"), object: myDict) 

NotificationCenter.default.addObserver(self, selector: #selector(refreshList(_:)), name: NSNotification.Name(rawValue: "refresh"), object: nil)
goto
  • 7,908
  • 10
  • 48
  • 58
Saurabh Sharma
  • 1,671
  • 2
  • 13
  • 16
  • This works for sure. But NSNotification is just a glorified event bus singleton. You can pass anything to anywhere. If you want to build a system to scale, this will create more problem than it solves later. I would stick with delegate, closure, or unwind segue if they're view controllers. – X.Y. Feb 03 '18 at 20:59
0

There is protocol there is closure. With closure, we need to avoid memory leaks by using weak self (or unowned self). With protocol, there would be one per viewController that you want to "monitor", end up with dozens of delegate to implement. Here I've another simple solutions in Swift:

Inside a new file or existing one (for example: UIViewController+Extensions.swift), create this protocol:

protocol ViewControllerBackDelegate: class {
    func back(from viewController: UIViewController)
}

Inside LEVEL-2 viewController, where you want a callback when Back is pressed from:

class LevelTwoViewController: UIViewController {
    // making this a weak variable so that it won't create a strong reference cycle
    weak var delegate: ViewControllerBackDelegate? = nil

    override func willMove(toParentViewController parent: UIViewController?) {
        super.willMove(toParentViewController: parent)
        if (parent == nil) {
            delegate?.back(from: self)
        }
    }
}

Since delegate is optional, you may add this code to a base class of your view controllers. I would add to where it needs to be.

In your LEVEL-1 viewController, assume you calling LEVEL-2 via a segue in Storyboard:

class LevelOneViewController: UIViewController {
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "Go to Level 2") {
            if let vc = segue.destination as? LevelTwoViewController {
                vc.selectedItems = self.selectedItems // passing data-in
                vc.delegate = self
            }
        }
        // repeat `if` for another sub-level view controller
    }
}

extension LevelOneViewController: ViewControllerBackDelegate {    
    func back(from viewController: UIViewController) {
        if let vc = viewController as? LevelTwoViewController {
            self.selectedItems = vc.selectedItems
            // call update if necessary
        }
        // repeat `if` for another sub-level view controller
    }
}
  • only one protocol is required.
  • only one extension per first-level viewController.
  • no modifications to sub-level viewController if more/less data need to return
  • handle data-out just like data-in in prepare(for:sender:)
John Pang
  • 2,403
  • 25
  • 25
0
//FirstViewController

import UIKit

class FirstViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
     
}

@IBAction func pushToSecond(_ sender: Any) {
    if let vc = storyboard?.instantiateViewController(withIdentifier: "SecondViewController")as? SecondViewController {
         vc.callBack = { (id: String,name: String,age: Int) in
            print(id,name,age)
        }
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

}

// //SecondViewController

import UIKit

class SecondViewController: UIViewController {

var callBack: ((_ id: String, _ name: String, _ age: Int)-> Void)?

@IBAction func BackToFirstWitData(_ sender: Any) {
    
    callBack?("1","Test",22)
    self.navigationController?.popViewController(animated: true)
    
}
override func viewDidLoad() {
    super.viewDidLoad()
    
    // Do any additional setup after loading the view.
}

}
Rohit
  • 3,401
  • 4
  • 33
  • 60
-3

Here is how I would do it.

@interface ViewControllerA:UIViewController
@property(strong, nonatomic) ViewControllerB * recieverB;
@end

@implementation ViewControllerA
//implement class
- (void)prepareForSegue:(UIStoryboardSegue *) sender:(id)sender
{
segue.destinationViewController.recieverA = self;
}
-(void)viewDidLoad
{
//stop strong refrence cycle
self.viewControllerB = nil;
}
@end

Class B

@interface ViewControllerB:UIViewController
@property(strong, nonatomic, getter = parentClass) ViewControllerB * recieverA;
@end

@implementation ViewControllerB
//implement class
- (void)viewWillDisappear:(BOOL)animated
{
parentClass.recieverB = self;
//now class A will have an instance on class b
}
@end

I didn't put the #import

  • Should never capture a strong reference on another view controller. Use weak delegate. Also this code doesn't show how to pass data from `ViewControllerB` back to `ViewControllerA`. Not even anything close to the question of OP. – John Pang Sep 13 '18 at 16:58