Passing Data Between 2 View Controllers in Swift

Passing data between a ViewController to another ViewController with navigation controller Swift 5

If you are using segue, then add "segue_identifier" in storyboard and the in secondviewcontroller add:

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue_ identifier" {
let mainTab = segue.destination as! tabBarController
let nav = mainTab.viewControllers[0] as! UINavigationController
let vc = nav.viewControllers.first as! HomeViewController
vc.cid = cityId
}
}

Because your segue destination is UINavigationController, you need to send data to view controller like this

Passing data between view controllers

This question seems to be very popular here on Stack Overflow so I thought I would try and give a better answer to help out people starting in the world of iOS like me.

Passing Data Forward

Passing data forward to a view controller from another view controller. You would use this method if you wanted to pass an object/value from one view controller to another view controller that you may be pushing on to a navigation stack.

For this example, we will have ViewControllerA and ViewControllerB

To pass a BOOL value from ViewControllerA to ViewControllerB we would do the following.

  1. in ViewControllerB.h create a property for the BOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. in ViewControllerA you need to tell it about ViewControllerB so use an

     #import "ViewControllerB.h"

Then where you want to load the view, for example, didSelectRowAtIndex or some IBAction, you need to set the property in ViewControllerB before you push it onto the navigation stack.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
viewControllerB.isSomethingEnabled = YES;
[self pushViewController:viewControllerB animated:YES];

This will set isSomethingEnabled in ViewControllerB to BOOL value YES.

Passing Data Forward using Segues

If you are using Storyboards you are most likely using segues and will need this procedure to pass data forward. This is similar to the above but instead of passing the data before you push the view controller, you use a method called

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

So to pass a BOOL from ViewControllerA to ViewControllerB we would do the following:

  1. in ViewControllerB.h create a property for the BOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. in ViewControllerA you need to tell it about ViewControllerB, so use an

     #import "ViewControllerB.h"
  3. Create the segue from ViewControllerA to ViewControllerB on the storyboard and give it an identifier. In this example we'll call it "showDetailSegue"

  4. Next, we need to add the method to ViewControllerA that is called when any segue is performed. Because of this we need to detect which segue was called and then do something. In our example, we will check for "showDetailSegue" and if that's performed, we will pass our BOOL value to ViewControllerB

     -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
    ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
    controller.isSomethingEnabled = YES;
    }
    }

If you have your views embedded in a navigation controller, you need to change the method above slightly to the following

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:@"showDetailSegue"]){
UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
controller.isSomethingEnabled = YES;
}
}

This will set isSomethingEnabled in ViewControllerB to BOOL value YES.

Passing Data Back

To pass data back from ViewControllerB to ViewControllerA you need to use Protocols and Delegates or Blocks, the latter can be used as a loosely coupled mechanism for callbacks.

To do this we will make ViewControllerA a delegate of ViewControllerB. This allows ViewControllerB to send a message back to ViewControllerA enabling us to send data back.

For ViewControllerA to be a delegate of ViewControllerB it must conform to ViewControllerB's protocol which we have to specify. This tells ViewControllerA which methods it must implement.

  1. In ViewControllerB.h, below the #import, but above @interface you specify the protocol.

     @class ViewControllerB;

    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
  2. Next still in the ViewControllerB.h, you need to set up a delegate property and synthesize in ViewControllerB.m

     @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. In ViewControllerB we call a message on the delegate when we pop the view controller.

     NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
  4. That's it for ViewControllerB. Now in ViewControllerA.h, tell ViewControllerA to import ViewControllerB and conform to its protocol.

     #import "ViewControllerB.h"

    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
  5. In ViewControllerA.m implement the following method from our protocol

     - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
    NSLog(@"This was returned from ViewControllerB %@", item);
    }
  6. Before pushing viewControllerB to navigation stack we need to tell ViewControllerB that ViewControllerA is its delegate, otherwise we will get an error.

     ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.delegate = self
    [[self navigationController] pushViewController:viewControllerB animated:YES];


References

  1. Using Delegation to Communicate With Other View Controllers in the View Controller Programming Guide
  2. Delegate Pattern

NSNotification center

It's another way to pass data.

// Add an observer in controller(s) where you want to receive data
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeepLinking:) name:@"handleDeepLinking" object:nil];

-(void) handleDeepLinking:(NSNotification *) notification {
id someObject = notification.object // Some custom object that was passed with notification fire.
}

// Post notification
id someObject;
[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

Passing Data back from one class to another (A class can be any controller, Network/session manager, UIView subclass or any other class)

Blocks are anonymous functions.

This example passes data from Controller B to Controller A

Define a block

@property void(^selectedVoucherBlock)(NSString *); // in ContollerA.h

Add block handler (listener)

Where you need a value (for example, you need your API response in ControllerA or you need ContorllerB data on A)

// In ContollerA.m

- (void)viewDidLoad {
[super viewDidLoad];
__unsafe_unretained typeof(self) weakSelf = self;
self.selectedVoucherBlock = ^(NSString *voucher) {
weakSelf->someLabel.text = voucher;
};
}

Go to Controller B

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ControllerB *vc = [storyboard instantiateViewControllerWithIdentifier:@"ControllerB"];
vc.sourceVC = self;
[self.navigationController pushViewController:vc animated:NO];

Fire block

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
NSString *voucher = vouchersArray[indexPath.row];
if (sourceVC.selectVoucherBlock) {
sourceVC.selectVoucherBlock(voucher);
}
[self.navigationController popToViewController:sourceVC animated:YES];
}

Another Working Example for Blocks

Swift 3 - Passing data between a View Controller and after that to another 2

Before calling presentViewController add :

nextViewController.name = yourTextField.text

You could also delete the segue call. That is redundant.

Here is an example that I've used in the past :

    @IBAction func doSegue(_ sender: UIButton) {
buttonTag = sender.tag

let storyboard = UIStoryboard (name: "Main", bundle: nil)
let resultVC = storyboard.instantiateViewController(withIdentifier: "ResultViewController")as! ResultViewController

// Communicate with new VC - These values are stored in the destination
// you can set any value stored in the destination VC here
resultVC.firstValue = buttonTag
resultVC.secondValue = randomOpponentValue()
self.navigationController?.pushViewController(resultVC, animated: true)
}

Problem while passing data between multiple view controllers using push pop navigation

You can use NotificationCenter to receive data.

On press of your Get Data button in your ThirdViewController, post your notification.

let myDataToPass : [String: Any] = ["myData" : yourStringValue]
NotificationCenter.default.post(name: Notification.Name("getDataPressed"), object: myDataToPass)

In your FirstViewController, add observer in viewDidLoad that will listen for the notification:

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(receiveInitialDetails(notification:)), name: Notification.Name("getDataPressed"), object: nil)
}

//once the notification is received, this function will be called and you can get the data that you passed in thirdVC using notification.userInfo
@objc func receiveInitialDetails(notification : Notification) {
let data = notification.userInfo as! [String:Any]
let yourStringValue = data["myData"] as! String
// you have your data here. downcast it to your desired data type.
}

What's the easiest way to pass data between two simultaneously displayed view controllers?

Creating Delegates

Start with creating delegates for both of your container ViewControllers. Don't forget to add : class. If you didn't do it, you wouldn't be able to create weak delegate variable:

protocol TopViewControllerDelegate: class {
func sendMessage(_ string: String)
}
protocol BottomViewControllerDelegate: class {
func sendMessage(_ string: String)
}

Now for every container ViewController create weak delegate variable

class TopViewController: UIViewController {
weak var delegate: TopViewControllerDelegate?
...
}

class BottomViewController: UIViewController {
weak var delegate: BottomViewControllerDelegate?
...
}

then for TopVC implement Bottom's delegate and for BottomVC Top's.

extension TopViewController: BottomViewControllerDelegate {
func sendMessage(_ string: String) {
// do something
}
}
extension BottomViewController: TopViewControllerDelegate {
func sendMessage(_ string: String) {
// do something
}
}

Assigning Delegates

Your segues between main ViewController and containers should have their own identifiers: EmbedTop, EmbedBottom.

So in your WrapperViewController create variable for your Top and Bottom ViewController and override method prepare(for:sender:) and inside assign these variables

class WrapperViewController: UIViewController {

var topVC: TopViewController?
var bottomVC: BottomViewController?

...

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedTop" {
topVC = segue.destination as! TopViewController
} else if segue.identifier == "EmbedBottom" {
bottomVC = segue.destination as! BottomViewController
}
}

}

finally in viewDidAppear set delegate of TopVC's as BottomVC and of BottomVC's as TopVC

override func viewDidAppear(_ animated: Bool) {
topVC.delegate = bottomVC
bottomVC.delegate = topVC
}

Now your two ViewControllers can speak with each other! :-)


Example:

class BottomViewController: UIViewController {
...
func speakToTop() {
delegate?.sendMessage("Hi Top!")
}
}

passing data between view controllers programmatically Swift

you need to follow some steps

step1

initially embed with your initial VC to navigation controller , for e.g

Now select the first controller in Storyboard and click on Editor > Embed in... > Navigation Controller.

Sample Image

step2

Now you have to link the second Controller in Storyboard with your new SecondViewController.swift file.

Select the yellow circle at the top of the controller, click on the Identify inspector panel icon on the right side of the XCode window, and type the name of your new .swift file in the Class and StoryboardID fields

for e.g

Sample Image

step3

Passing a String

Now select the other controller in Storyboard and add this variable right below the SecondViewController class declaration:

 class SecondViewController: UIViewController {

let secondLabel = UILabel()

var stringPassed = ""

Make the app assign the value of this variable to secondLabel with the following line of code in the viewDidLoad() method

step4

on your first VC , inside the button

func buttonTarget() {

let myVC = storyboard?.instantiateViewControllerWithIdentifier("SecondVC") as! SecondViewController
myVC.stringPassed = label.text!

navigationController?.pushViewController(myVC, animated: true)

}

finally you get the out put as

 func setupLabelSecond() {

secondLabel.frame = CGRect(x: 40, y: 80, width: 300, height: 60)

if let outputText = stringPassed
{
secondLabel.text = outputText
}else
{
secondLabel.text = "this is Second Page"
}
secondLabel.textColor = UIColor.yellow
secondLabel.font = UIFont.boldSystemFont(ofSize: 25)
secondLabel.textAlignment = .center
secondLabel.layer.borderWidth = 2
secondLabel.layer.borderColor = UIColor.yellow.cgColor
secondLabel.layer.cornerRadius = 5
view.addSubview(secondLabel)
}

for sample you can get the tutorial here

update for XIB

   func buttonTarget() {

var vcPass = SecondViewController(nibName: "SecondViewController", bundle: nil)
vcPass.stringPassed = label.text!
self.navigationController?.pushViewController(vcPass, animated: true)

}

update for without XIB and Storboard

change your appdelegate

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
// Override point for customization after application launch.

let navigation = UINavigationController(rootViewController: MainViewController())
self.window?.rootViewController = navigation
self.window?.makeKeyAndVisible()
return true
}

on your MainViewController

 func buttonTarget() {

var vcPass = SecondViewController()
vcPass.stringPassed = label.text!
self.navigationController?.pushViewController(vcPass, animated: true)

}

Passing a String

Now select the other controller in Storyboard and add this variable right below the SecondViewController class declaration:

 class SecondViewController: UIViewController {

let secondLabel = UILabel()

var stringPassed = ""

Make the app assign the value of this variable to secondLabel with the following line of code in the viewDidLoad() method

func setupLabelSecond() {

    secondLabel.frame = CGRect(x: 40, y: 80, width: 300, height: 60)

if let outputText = stringPassed
{
secondLabel.text = outputText
}else
{
secondLabel.text = "this is Second Page"
}
secondLabel.textColor = UIColor.yellow
secondLabel.font = UIFont.boldSystemFont(ofSize: 25)
secondLabel.textAlignment = .center
secondLabel.layer.borderWidth = 2
secondLabel.layer.borderColor = UIColor.yellow.cgColor
secondLabel.layer.cornerRadius = 5
view.addSubview(secondLabel)
}

Passing Data Between View Controllers in Swift 4

Your problem is that your prepare methods never run because you never call them.

Look, when you call performSegue, then prepare(for segue: sender:) is called too, so you can override this method in your ViewController and because you're passing identifier as parameter of performSegue method you can determine what should happen if segue has this or this identifier

So, delete prepare for segue methods from IBActions

@IBAction func backButton(_ sender: Any) {
if installer == "verified"{
performSegue(withIdentifier: "main/login", sender: self)
}
}

@IBAction func button1(_ sender: Any) {
if installer == "verified"{
performSegue(withIdentifier: "Button1", sender: self)
}
}

instead override prepare(for segue: sender:) method of ViewController and inside specify what should happen if segue has "main/login" indentifier or "Button1":

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "main/login" {
let mainController = segue.destination as! ViewController
mainController.myvar = installer
} else if segue.identifier == "Button1"
let IPController = segue.destination as! IP_ModuleQuickStartViewController
IPController.verified = installer
}
}


Related Topics



Leave a reply



Submit