-1

Suppose I have a storyboard like so:

Storyboard image

Is it possible for me to get a flag or a boolean data from A back to B? I initially thought of using delegation but most of the tutorials about it talks about sending data between UIViewControllers that are part of 1 NavigationController. In my case, the UIViewController I need to get data is outside of the navigation controller. Is there a way for me to send data from A to B despite not being embedded in the same NavigationController?

kobowo
  • 2,529
  • 2
  • 24
  • 37

8 Answers8

2

If you don't want to use delegate between the classes . One possible way is to create separated file , saved in class and fetch required data any where in navigation .

Useful class for your case would be create singleton class FlowEngine . Use getter / setter method for saving and fetching of data. Code is attached for your reference .

class FlowEngine : NSObject{

    private let static shared =  FlowEngine()

    private var data : String

    private init(){

    }

    func savedData(text : String){
       data  = text
    }

    func fetchSavedData() -> String{

      return data  // add checsk for nil values
    }

}
Vasucd
  • 357
  • 2
  • 10
  • 1
    I appreciate this Idea! The only thing is the developer has to ensure read the data on the controller in the navigation stack since no callbacks or notifications posted when there are any updates. – Vikram Parimi Dec 18 '19 at 10:17
0

if your A viewController is not huge, In B viewController do this :

class B : UIViewController {

  var a : A! = nil


    func viewDidLoad() {
      super.viewDidLoad()


    a = storyboard?.instantiateViewController(withIdentifier: "StoryBoard ID") as? A
    if a.booleanValue == true { 

        // use your booleanValue
       a = nil // deallocate after using your value.
   }

}


 }
Celeste
  • 1,519
  • 6
  • 19
0

Delegation doesn't require the ViewControllers to be in same navigation stack. You can use the same for your case. However, if you choose to go with NotificationCenter, just remember to remove the observer when appropriate.

The Pedestrian
  • 460
  • 2
  • 11
0

Other answers seem to accomplish your requirements but for the sake of completeness you could try to use KVC and KVO for modifying values in A and receiving its changes in B (or any other place)

You could see a detailed explanation of how to use them in here.

Miguel Isla
  • 1,379
  • 14
  • 25
0

You have several ways to go, depending on your needs :

  1. Delegation

Declare a protocol in A, and make B conform to it. Set the delegate of A to B. This could be cumbersome if the navigation stack has too many level, as you would need to pass the reference of B to each ViewController between A & B

  1. Notification / KVO

B subscribe to a notification sent by A, no reference needed, thread safe. Don't forget to unsubscribe when done.

  1. Proxy class

Use a proxy singleton class, that will hold your data. A will write to it, and B will read it in viewWillAppear.

  1. UserDefaults

Same concept as a Proxy Class, but the data will persist during your app life cycle and even after killing the app. It's appropriate if you want to change a flag or a setting for your user, not if you have a lot of data to hold.

Neimsz
  • 1,554
  • 18
  • 22
0

Cocoa Touch uses the target-action mechanism for communication between a control and another object. More here... If you would like to use it with UIControl objects like buttons, then you can set it in Interface Builder by sending an action to the FirstResponder object.

Target-Action will start searching a VC which responds to a given method from the current first responder and then will move to the next responder and will terminate a search in a current UIWindow. Once a controller which responds to a method signature is found, the search is terminated.

class AViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }


    @IBAction func configure(with dictionary: Dictionary<String, Any>) {
        print(dictionary)
    }
}

class BViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let a = self.targetViewController(forAction: #selector(ViewController.configure(with:)), sender: self) as? ViewController
        a?.configure(with: ["firstName": "Alex", "lastName": "Toto"])

    }
}
Blazej SLEBODA
  • 8,936
  • 7
  • 53
  • 93
0

Update (better solution)

We've had to edit a few things to the functionality which presented me with the opportunity to refactor this. I used the NSNotification way, which was way cleaner than using closures.

ViewControllerB

override func viewDidLoad() {
    super.viewDidLoad()
    //Observe for notification from "myIdentifier"
    NotificationCenter.default.addObserver(self, selector: #selector(self.processNotification(notification:)), name: Notification.Name("myIdentifier"), object: nil)
}

//function that gets called when notification is received
//the @objc annotation is required!
@objc func processNotification(notification: Notification) {
    //Do something
}

ViewControllerA

@IBAction func didTapButton(_ sender: Any) {
    //Process something
    // ...
    //

    //Post a notification to those observing "myIdentifier"
    NotificationCenter.default.post(name: Notification.Name("myIdentifier"), object: nil)
    self.dismiss(animated: true, completion: nil)
}

Old (but working) solution

This might be an unpopular solution but I managed to solve this with callbacks. I was looking into another possible solution which was commented NSNotification but since someone from the team already had experience with using callbacks in this manner, we decided to ultimately use that.

How we made it work:

ViewControllerB is given the actual code implementation through prepare(for segue: UIStoryboardSegue, sender: Any?) while ViewControllerC (This is the middle UIViewController in the picture) has a callback property and ViewControllerA contains the value to pass when it's about to be dismissed.

ViewControllerB

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   if segue.identifier == "secondSegue" {
      let nvc: NavigationController = segue.destination as! NavigationController
      let vc = nvc.viewControllers[0] as! ViewControllerC
      vc.completion = { hasAgreed in 
         //Do Something
      }
   }
} 

ViewControllerC

class ViewControllerC: UIViewController {
   var completion: ((Bool) -> ())?

   override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   if segue.identifier == "thirdSegue" {
      let nvc: NavigationController = segue.destination as! NavigationController
      let vc = nvc.viewControllers[1] as! ViewControllerA
      vc.middleController = self
   }
} 

ViewControllerA

class ViewControllerC: UIViewController {
   var middleController: ViewControllerC?

   @IBAction func didTapButton(_ sender: Any) {
      self.dismiss(animated: true, completion: {
         middleController?.completion(true)
      }) 
   }
} 

With this, we got the data we needed from the diagram picture above.

kobowo
  • 2,529
  • 2
  • 24
  • 37
-1

Your best bet is to make use of NotificationCenter to achieve this.

Post notification like this:

NotificationCenter.default.post(name: Notification.Name("NotificationName"), object: nil, userInfo: ["somekey":"somevalue"])

Observe it like this:

NotificationCenter.default.addObserver(self, selector: #selector(self.dataReceived(notification:)), name: Notification.Name("NotificationName"), object: nil)

Use the following method:

@objc func dataReceived(notification: Notification) {}
atulkhatri
  • 10,896
  • 3
  • 53
  • 89