Passing Data Between Two Viewcontrollers (Delegate) - Swift

Passing data between two ViewControllers (delegate) - Swift

1) You need to set delegate into prepareForSegue:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let viewController = segue.destinationViewController as? SecondVC {
viewController.delegate = self
}
}

UPDATE:

2) Set delegate as Optional

class SecondVC: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
var delegate: SendDataDelegate?
...

func pickerView (pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
var text = arr[row]
dispatch_async(dispatch_get_main_quene(), {
self.delegate?.sendData(text)
)}
}

Delegate/Protocols Passing data from one view controller to another

The most obvious problem lies here:

if let push = self.storyboard?.instantiateViewController(withIdentifier: "MainScreenVC") as? MainScreenVC {
push.transferCurrencyDelegate = self
}

You have to realize that instantiateViewController creates a new view controller - it's not the reference to the view controller presented at the screen. In that code you just created a completely new view controller and then set its delegate to self, but otherwise nothing else.

Without knowing the context it is really hard to suggest anything - prepare(for:) segue might be the place where you want to set the delegate. Anyway, the problem is that you have to obtain a reference to the controller that is presented on the screen, the one that is supposed to be reacting to those events.

Moreover, from the memory management aspect, you should really consider making the delegate property a weak one to prevent memory leaks.

EDIT

So after seeing the minimal working example you provided at link, I think I can provide the solution on how to get that string to the SecondVC.

Your first view controller with comments:

import UIKit

class ViewController: UIViewController {

var newLine: String = "EUR"

@IBAction func push(_ sender: Any) {
// here the secondVC does not exist yet, calling delegate.transferWord() here would have no sense
// performSegue will create that secondVC, but now it does not exist, nor it is set up as the delegate
self.performSegue(withIdentifier: "ViewController", sender: navigationController)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let secondVC = segue.destination as? SecondVC, segue.identifier == "ViewController" {
// at this moment secondVC did not load its view yet, trying to access it would cause crash
// because transferWord tries to set label.text directly, we need to make sure that label
// is already set (for experiment you can try comment out next line)
secondVC.loadViewIfNeeded()
// but here secondVC exist, so lets call transferWord on it
secondVC.transferWord(word: newLine)
}
}
}

No need for delegates here, because your ViewController is the one pushing the SecondVC to the Navigation controller - that means that you can access it directly in prepare(for:), as you can see above.

Now the SecondVC is super simple (I omitted unnecessary code):

import UIKit

class SecondVC: UIViewController {

@IBOutlet weak var label: UILabel!

func transferWord(word: String) {
label.text = word
}
}

Storyboards can stay as they are.

Setting multiple delegates for passing multiple view controllers right way?

What you're doing is basically correct for the architecture you have chosen:

  • If you have a rule that only VC1 knows the full data and can save it, then you need to pass changes in the data back to VC1.

  • If you have a rule that the way to pass the data back to VC1 is to use a protocol-and-delegate architecture, then you have to make VC1 the delegate for your protocol(s). There is nothing wrong with a single object (your VC1) being extended to adopt multiple protocols; that, indeed, is a standard Swift pattern.

However, I would argue that the reason this feels ugly to you is that those are not very good rules. I'll address them in reverse order:

Why delegation? In modern Swift, there are cleaner ways to pass data around. Modern app architectures are typically reactive, meaning that a handler of data is a publisher of the changes in that data, and the central store of the data subscribes to those publishers. Thus the data store can hear directly of any changes without the view controllers having to send those changes to anyone in particular.

Why a view controller? This is arguably the weakest point in your architecture. What business is it of a view controller to know anything about the data store and the mechanism for saving it? You should have a separate data store object retained by some securely persistent object such as the app delegate. This object stores and gatekeeps the data, and knows (or has a service helper who knows) how to persist it. You can use dependency injection to hand a reference to this object to every view controller — which also has the added benefit of making your code eminently testable. Even better, if you accompany this idea with the use of reactiveness that I outlined in the previous paragraph, you will have a clean "touch-free" architecture that just works, where every object does its own job and no other.

How to pass data between UIViewControllers with protocols/delegates

Objects don't exactly listen for method calls. They sit there, waiting to invoked.

The line

self.delegate?.messageData(inputMessage.text!)

From your SenderViewController is a function call. (The term method and function are pretty much interchangeable, although the method is usually used for the functions of objects.) It invokes the function messageData in ViewController.

Passing data between two views (in the same ViewController)

Let's say you have two views view1 and view2.
For example some data changed in view2 and you need to pass this data to view1. I'd do this with delegation pattern. So here's the setup:

protocol View2Delegate {
func didChangeSomeData(data: String)
}

Now in view2

class View2: UIView {

var delegate: View2Delegate?

var text: String = String() {
didSet {
self.delegate.didChangeSomeData(data: text)
}
}
}

and in view1

class View1: UIView, View2Delegate { 

var textToChange: String = String()

func didChangeSomeData(data: String) {
// Do whatever you want with that changed data from view2
textToChange = data
}
}

and in your viewController

class MyViewController: UIViewController {
var: view1 = View1()
var: view2 = View2()

func viewDidLoad() {
super.viewDidLoad()
view2.delegate = view1
}

Now, if you don't really want to couple view1and view2, you could listen via the same pattern changes of view2 in ViewController and then pass it directly to the view1 via property accessor.

class MyViewController: UIViewController, View2Delegate {
var: view1 = View1()
var: view2 = View2()

func viewDidLoad() {
super.viewDidLoad()
view2.delegate = self
}

func didChangeSomeData(data: String) {
view1.textToChange = data
}

More on protocols in the Apple's Documentation

Update

You could also go with Key Value Observing pattern. You could setup it like this:

class MyViewController: UIViewController {
var view1 = View1()
var view2 = View2()

override func viewDidLoad() {
super.viewDidLoad()

let observation = view2.observe(\.text) { (observed, change) in
if let newValue = change.newValue {
view1.textToChange = newValue
}
}

}

}


Related Topics



Leave a reply



Submit