Finding It Difficult to Pass Data to Separate Viewcontroller

Finding it difficult to pass data to separate viewController

First of all when you initiate viewControllerA :

let vc = viewControllerA()

You are creating new instance of viewcontrol which doesn’t reference to your first view control.

You can pass data to viewcontrollers in different ways.
You can use delage pattern or you can use unwind.

In delegate method first you define a class type protocol with a function definition for changing something in viewControllerA.

protocol ViewControllerBDelegate: class {
func changeSwitch(toValue: Boolean)
}

Then in ViewControllerB you define a weak reference to delegate

weak var delegate: ViewControllerBDelegate?

Then you adopt this protocol on ViewControlA:

extension ViewControllerA: ViewControllerBDelegate {
func changeSwitch(toValue: Boolean) {
state = toValue
}
}

When you want to present or push to ViewControllerB you should set this variable to self

let vc = ViewControllerB()
vc.delegate = self
present(vc, animated: true, completion: nil)
// or navigationController. pushViewController(vc, animated: true)

if you are using segue to navigate from one viewcontroller to another, you should set delegate variable in prepare(for segue, sender). Override this function in ViewControllerA

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mySegue" ,
let vc = segue.destination as? ViewControllerB {
vc.delegate = self
}
}

Then when switch value changed you can use delegate to change value in ViewControllerA

delegate?.changeSwitch(toValue: sender.isOn)

on wind let you pop or dismiss child viewcontrollers to a certain parent and then do something. You can read a full tutorial here

EDIT

for chain delegates you can pass a delegate to ViewController B, then pass the same delegate to ViewController C.

in view controller C you define the same type delegate

weak var delegate: ViewControllerBDelegate?

then in view controller B when you are navigating to view controller c you pass the same delegate

let vc = ViewControllerC()
vc.delegate = self.delegate
present(vc, animated: true, completion: nil)

EDIT 2

SWRevealViewController is different scenario. revealController have an property named frontViewController. which can be your ViewControllerA if you dont push any other controllers on reveal. handling it with frontViewController is tricky you should be sure if frontController is ViewControllerA.

so i suggest you use another method to communicate with ViewControllerA. you can use NotificationCenter.

extension Notification.Name {
static let updateMap = Notification.Name("updateMap")
}

in ViewControllerA

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateMap(_:)), name: .updateMap, object: nil )
}

@objc func updateMap(notification: NSNotification) {
if let state = notification.userInfo?["state"] as? Bool {
// do something with state
}
}

and in ViewControllerC you post a notification when switch value is Changed:

let userInfoDic:[String: Bool] = ["state": sender.isOn]
// post a notification
NotificationCenter.default.post(name: .updateMap, object: nil, userInfo: userInfoDic)

if frontViewController in reveal is pushed again. reveal will initiate
new ViewControllerA for frontViewController. in this scenario you have
to set settings in UserDefault and in ViewControllerA read this
settings.

using UserDefaults :

in ViewControllerC

UserDefaults.standard.setValue(sender.isOn, forKey: "mapState")

in ViewControllerA

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let state = UserDefaults.standard.value(forKey: "mapState") ?? false
self.mapView.isTrafficEnabled = state
}

Can't pass data to another view controller in when preparing for segue

The error is pretty informative. It says the class ViewController has no public or internal property called data. You'll have to declare a property called data in class ViewController.

class ViewController: UIViewController {

var data: String?

}

How to pass data between view controllers - not working

This is an inefficient way to do this as you are passing data through 3 different objects. However, going on with this methodology, the problem is the label's are not created yet inside

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {

VC.player1ScoreLabel.text = String(player1CurrentScore)
VC.player2ScoreLabel.text = String(player2CurrentScore)
VC.player3ScoreLabel.text = String(player3CurrentScore)
VC.player4ScoreLabel.text = String(player4CurrentScore)

}
}

See, the label isn't created yet. So, you are setting text on aUILabel that isn't initialized. Therefore, you need to create variables for the labels inside ThirdViewController.

Third View Controller

class ThirdViewController: UIViewController {

@IBOutlet var player1ScoreLabel: UILabel!
@IBOutlet var player2ScoreLabel: UILabel!
@IBOutlet var player3ScoreLabel: UILabel!
@IBOutlet var player4ScoreLabel: UILabel!

var score0:Int!
var score1:Int!
var score2:Int!
var score3:Int!

override func viewDidLoad() {
super.viewDidLoad()
self.player1ScoreLabel.text = String(score0)
self.player2ScoreLabel.text = String(score1)
self.player3ScoreLabel.text = String(score2)
self.player4ScoreLabel.text = String(score3)
}
}

and change the segue in SecondViewController

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {

VC.score0 = player1CurrentScore
VC.score1 = player2CurrentScore
VC.score2 = player3CurrentScore
VC.score3 = player4CurrentScore

}
}

Another Way:

Let's create a singleton called Game. Within this (assuming you only have 4 players) we can create 4 players that will never change. This allows us to create instances of players in one location and call upon them as necessary.

NOTE: A singleton can be misused EASILY.

https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift
https://cocoacasts.com/are-singletons-bad/

class Game {

static var score0:Int = 0
static var score1:Int = 0
static var score2:Int = 0
static var score2:Int = 0

}

Then, anywhere in your code you can access Game.score0, Game.score1.

CAUTION:

I would caution you to very carefully use singletons. You don't want everything with public access. You need to determine if this is good for you or not. Cheers.

Passing data to another ViewController without moving to the ViewController

you can use class and create singleton object to store data and retrieve
in fifth view controller

class SingletonClass {
var sharedInstance: SingletonClass {
struct Static {
static let instance = SingletonClass()
}
return Static.instance
}
var dataText : String = ""
}

now you can store data in singleton object like this as below

 let singleTon = SingletonClass()
singleTon.sharedInstance.dataText = "store data"

and use like this in your fifthViewController

  let singleTon = SingletonClass()
print(singleTon.sharedInstance.dataText)

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

Pass Data Between Two Unrelated ViewControllers

Here are two solutions to the problem, both better (IMO) than delegation or the defaults system, and hugely better than singletons, static variables, global variables.

1. Pass the data through B.

You've got a structure that's like this: A->B->C, and C needs some data that A has. So the view controllers are not really unrelated, they're just not directly connected. A good strategy for handling this is to pass the necessary information through B. That is, A doesn't necessarily know about C, probably shouldn't need to know. And C doesn't need to know about A either. From the perspective of A, there's a job to do and a certain amount of information required to do it. A should therefore provide all the necessary information to B during the A->B segue. If B uses C to get part of its job done, that's fine, but A doesn't care about that. Likewise, C doesn't know about A or even B -- all C knows is that it's given some data to do a job. Since it's B that's instantiating (by means of a segue) C, it's B's responsibility to provide the necessary information.

The same is true going in the other direction. If C generates some data that A will eventually need, B should retrieve it from C before C goes away, and A should retrieve it from B.

The only reason you'd need to make A a delegate of C is if A needs to find out about updates as soon as they occur. If that happens, though, it's probably a good indication that you should move on to the next option...

2. Use your data model.

It's easy to forget that the M in MVC is supposed to be an equal partner in applications. We often just let our view controllers manage all the data, and then try to figure out how to get the right data to flow through the view controller graph to the right places. That can make your application a lot less flexible than it could be: in a A->B->C situation like the one you've got, you're suddenly prevented from inserting another view controller between B and C, for example, because the new guy doesn't know that it needs to pass certain data through from B to C.

The answer is to use a data model -- an object or set of objects that manage the application's data. If you've got data generated in one controller (C in your case) that's needed by some other controller (like A), that data must be important to the overall application and not just to a single view controller. That's exactly the kind of data that should be managed as part of your data model. If you do that, then you don't have to worry about passing a ton of different data to every view controller that might need it, or that (like B) might need to pass it on to someone else. You only need to pass one thing -- a reference to the data model, or the relevant part of the data model.

Let's make this concrete and say that C is actually a "settings" view controller, and that the piece of data in question is the user's name. A needs to know about updates to the name so that it can display it properly. B doesn't care about the name at all. So, you set up some kind of Model class that includes a username property. The Model might be instantiated by the app delegate at startup, or by the root view controller A, and a reference to the model is passed on to each view controller in the view controller graph. Now C can set the username property in the model when the user changes it. A can read the same property to retrieve the data -- in fact, it can use KVO to find out about all the changes to the model that it cares about. A doesn't know about C or vice versa, and you don't have to bother with creating yet another delegate protocol just to pass some data around.

How to pass data from First ViewController directly to the Third View Controller using segue?

assign storybordID to your controller for ex here i have assigned "HomeVC" (it would be greate if you assign class name and storybordid same)

  let objthirdController = self.storyboard?.instantiateViewController(withIdentifier: "thirdControllerStoerybordID") as! thirdControllerclassname
objthirdController.variable = self.variable
self.navigationController?.pushViewController(objthirdController, animated: true)

How to pass data between multiple (in my setup 3) ViewControllers?

This is the solution basically: Swift : prepareForSegue with navigation controller

The problem was that the third view controller is embedded as a navigation controller. That is the reason why the prepareForSegue is different.

Solution is to use following prepareForSegue in the second VC:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let navigationVC = segue.destination as? UINavigationController, let myViewController = navigationVC.topViewController as? DViewController {
myViewController.currentCourse = self.currentCourse
}
}

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



Related Topics



Leave a reply



Submit