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 view1
and 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
Finding Distance Between Cllocationcoordinate2D Points
How to Add Custom Text in Nsdateformatter's Format String
Com.Facebook.Sdk Error 2 on iOS
Xcode 5/iOS 7 - Localization Not Working in Simulator
Using Sysctlbyname() from Swift
Apple MACh-O Linker (Id) Warning:Building for MACosx, But Linking Against Dylib Built for iOS
Uitableviewcellaccessorycheckmark and Autolayout Constraints
Programmatically Highlight Uibarbuttonitem
Simple Uitableview in Swift - Unexpectedly Found Nil
Enable Uialertaction of Uialertcontroller Only After Input Is Validated
iOS 7 When Rotating View in Tab Bar, Right Side of View Is Not Clickable
Static VS Class as Class Variable/Method (Swift)
Disable iOS Simulator 'Connect Hardware Keyboard' Programmatically
"Could Not Inspect Application Package" Xcode
How to Call Https Url in Uiwebview