How to cause an update of model to trigger a update in another controller in swift - ios

View Controller A has access to a model and can mutate the model. View Controller B displays the model. I want when View Controller A updates the model, a method to be triggered in View Controller B such that the view is updated. Is there a standard way to do this in Swift?

In the didset of this variable you can post a notification and the other controller can register for this notification. (We can do it more neatly depending on the requirement.)

If you make your model object an Objective-C object (#objc) then you can use KVO (key-value observering) to observe changes to it. If you do that then any object that's observing a property of the model will get notified when that property changes.

Here is one way you could do this.
Have whatever model you use, class or struct, implement a protocol that can be be used by view-controller B. view-controller B should not care what the model is, only that it conforms to the protocol it needs to extract the data it needs.
When view-controller A manipulates the model, post a notification with a reference to the model as an instance of the protocol, in the userInfo dictionary. view-controller B, subscribes to the notification and when triggered, extracts the reference from the userInfo and then gets the data it needs via the common protocol.

Another way to communicate between 2 view controllers is Protocol. Define a protocol, such as:
protocol ViewControllerAModelProtocol: class {
func viewControllerAModelDidUpdate()
}
In ViewControllerA, define a delegate type:
weak var updatedProtocol: viewControllerAModelDidUpdate?
When the model is updated and call:
updatedProtocol?.viewControllerAModelDidUpdate()
In ViewControllerB, implement the protocol:
class ViewControllerB: UIViewController, ViewControllerAModelProtocol {
func viewControllerAModelDidUpdate() {
// reloadData()
}
}
If ViewControllerB is presented or showed by ViewControllerA, set A's updatedProtocol to B:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let b = segue.destination as? ViewControllerAModelProtocol {
self.updatedProtocol = b
}
}

Related

What's the advantage of using delegate to transfer values between viewControllers?

I have two viewControllers, vcA and vcB.
When I want to transfer values to vcA from vcB, I use delegate like this:
//vcB.swift
protocol TransferDelegate {
func transferValue(msg: String)
}
var delegate: TransferDelegate!
//vcA.swift
class vcA: UIViewController {
}
extension vcA: TransferDelegate {
func transferValue(msg: String){
//xxxxxxx
}
}
When vcA push vcB,then vcb.delegate = self
When vcB want to transfer values, then delegate.transferValue(msg)
My question is what if I transfer vcA's instance self to vcB instead of set delegate like this:
//vcB.swift
class vcB: UIViewController {
var vca: vcA!
func transferValues(msg: String) {
vca.msg = msg
}
}
//vcA.swift
vcb.vca = self
I have tried both two methods, both of them can work.
So why we use the first method rather than the second?
What's the difference between then or what's the advantage of the delegate?
The advantage of using a delegate is that vcB doesn't have (or need) any knowledge of the specific object type acting as the delegate. And this in turn makes it much simpler for vcB to be used by more than just vcA. It makes your classes less coupled and much more flexible.
Imagine of a UITableView only worked with a specific type of data source and delegate instead of allowing for anything that conforms to the proper protocols to act as a data source and delegate. This is no different.
protocol TransferDelegate {
func transferValue(msg: String)
}
The good thing is you can reuse this delegate method in other vcs.
Now you have vcA and vcB to do things. Maybe in the future you need something similar in other vcs.
See when you are using delegate you can only access that particular method of that particular viewController. But if you save the whole instance in the vc2 you can access or change any of the properties or functions of that class. Might be which you don't want to allow the vc2 to access.

How to use delegate to performSegue in Swift

I have 3 classes :
Core that doesn't inherit from any class.
vcMain and vcIncomingFile which both inherit from UIViewController.
I have a segue from vcMain to vcIncomingFile.
How can I call the -performSegueWithIdentifier: method from the Core class,between vcMain and vcIncoming. I mean I want to have a method or delegate or anything else in Core class that can performSegue from vcMain to vcIncomingFile.
class Core {
func showIncomingVC(){ }
}
in showIncomingVC function, I want to performSegue between vcMain and vcIncomingFile.
Thanks
To implement the patterns properly we would need to know if your Core class is part of your model or if it is some kind of control flow mechanism.
If it is part of the model, it should know nothing about the view controllers but the view controllers are allowed to access it. In this case your view controllers could implement a delegate protocol of some sort and establish a call path from your Core class by assigning themselves as a delegate for some part of the Core functionality. The Core could then trigger any pre-defined behaviour in its delegates by calling the methods defined in the protocol.
For example,
If you define a protocol called FileEventDelegate with a method named fileReceived().
Then, add a member to your Core class called fileEventDelegate of type FileEventDelegate?
Whenever the Core class receives a file, it can call fileEventDelegate?.fileReceived(). The object instance, of whichever class implements the protocol, that registered itself as the delegate will handle it from there.
A unit testing class could also be the delegate and not even have nor need a segue to be performed.
Your Core class could even work without a delegate being set.
On the UI side, your VcMain class can implement the FileEventDelegate protocol by defining a fileReceived() function that call performSegueWithIdentifier(...). On viewLoaded() it can set itself as the fileEventDelegate of the Core class instance it is working with.
This keeps all model-to-visual logic in the viewController where it belongs.
If your Core class works in complete separation of the view controllers (meaning that the view controllers don't know how to access the instance(s) of Core), you may want to look into NSNotificationCenter and send notifications out in the universe for your viewControllers to pick-up asynchronously.
Core needs to be of UIViewController or some class that inherit UIViewController because performSegueWithIdentifier is a function of UIViewController
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/performSegueWithIdentifier:sender:
You can work with protocol instead. Create a protocol in your Core class with func showIncomingVC(){ } and use its delegate in your view class
Just do like this:
struct Segue {
let sourceVC: UIViewController
let sender: AnyObject
let identifier: String
}
protocol SegueProtocol: class {
var segue: Segue { get }
}
class Core {
weak var delegate: SegueProtocol?
func perform() {
guard let segue = delegate?.segue else {
return
}
segue.sourceVC.performSegueWithIdentifier(segue.identifier, sender: segue.sender)
}
}

Passing data between two View Controllers which aren't connected by a segue [duplicate]

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I know that you can pass information between two view controllers if they are connected by a segue using
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let destinationViewController = segue.destinationViewController as? searchTermViewController else { return }
destinationViewController.courseCodes = selectedCourses
}
}
The above code gives an error if there is no segue because of the .destinationViewController. How do i pass information between to arbitrary view controllers without having to set up a global variable?
You can set up a delegate pattern in order to do this.
Here are the steps for setting up the delegate pattern between two objects, where object A is the delegate for object B, and object B will send messages back to A. The steps are:
Define a delegate protocol for object B.
Give object B an optional delegate variable. This variable should be weak.
Make object B send messages to its delegate when something interesting happens, such as when it needs a piece of information. You write delegate?.methodName(self, . . .)
Make object A conform to the delegate protocol. It should put the name of the protocol in its class line and implement the methods from the protocol.
Tell object B that object A is now its delegate.
Here is a tutorial to give you a working example https://www.youtube.com/watch?v=9LHDsSWc680
Go to your storyboard, select the second view controller, go to the Identity inspector tab and give a StoryBoard ID value. This should be a unique value to identify your view controller.
Now in your first view controller', you can run this code. This will basically create an object of the second view controller, set the property value (for transferring data) and push it (same as the segue does)
let ctrl = self.storyboard?.instantiateViewControllerWithIdentifier("detailsView")
as? SecondViewController
ctrl?.userId = 250 // data to pass.
self.navigationController?.pushViewController(ctrl!, animated: true)
provided userId is a variable in your SecondViewController class. Replace
detailsView with the storyboard id value you gave earlier.
class SecondViewController: UIViewController {
var userId : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// do something with self.userId
}
}

Swift, how to tell a controller that another controller is its delegate

I'm learning Swift and I'm studying the delegation pattern.
I think I understand exactly what is delegation and how it works, but I have a question.
I have a situation where Controller A is the delegate for Controller B.
In controller B I define a delegate protocol.
In controller B I set a variable delegate (optional)
In controller B I send message when something happens to the delegate
Controller A must adopt method of my protocol to become a delegate
I cannot understand if every delegate controller (in this case A) listens for messages sent by controller B or If I have to tell to controller B that A is now his delegate.
I notice that someone use this code (in controller A)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Example" {
let navigationController = segue.destinationViewController as UINavigationController
let controller = navigationController.topViewController as AddItemViewController
controller.delegate = self
}
}
Is this the only way to tell a delegator who is his delegate?
I believe, you need to tell a deligator who is its delegate upon creation of that it. Now, the delegator can be created programatically or through storyboard. So, based on that you have two options, you can tell it who is its delegator programatically like you showed in the code or from IB.
The key here is upon creation. Let's me explain myself. Take the case of a UIView. Say, you want a Custom UIView object(CustomView). So, you drag and drop a UIView in your View Controller and in the identity inspector, you assign its class as of your CustomView's class. So, basically, as soon as the controller is created, your custom view will also be created. Now, you can either say it that the View Controller in which it is created is its delegate or You can go to the IB and connect the view's delegate to the View Controller.
Now, let's assume that you wanted the custom view to be created in your ViewController programatically. In that case, you would probably call the -initWithFrame: method to create the view and upon creation you tell that delegator that who is its delegate like-
myCustomView.delegate = self;
same goes with a View Controller.
controller.delegate = self;
So, basically to tell a delegator who is its delegate, you first need that delegator to be created. At least, that's what I think.
I think one of the best example of delegation is UITableView.
Whenever you want the control of various properties of a tableView e.g. rowHeight etc, you set your controller to be the delegate of your tableview. To set the delegate of your tableView you need to have tableView created obviously as pointed out by #natasha.
So in your case, you can set delegate of your delegator when you create it or when you find a need for the controller to be delegate of your delegator but you definitely need your delegator to be present to set its property.
You can set your controller as delegate at any time when you require control.
I'm sure you want your UIViewController to act like described, but here is a simpler example how to use the delegation pattern with custom classes:
protocol ControllerBDelegate: class {
func somethingHappendInControllerB(value: String)
/* not optional here and passes a value from B to A*/
/* forces you to implement the function */
}
class ControllerB {
var delegate: ControllerBDelegate?
private func someFunctionThatDoSomethingWhenThisControllerIsAlive() {
/* did some magic here and now I want to tell it to my delegate */
self.delegate?.somethingHappendInControllerB(value: "hey there, I'm a magician")
}
func doSomething() {
/* do something here */
self.someFunctionThatDoSomethingWhenThisControllerIsAlive()
/* call the function so the magic can really happen in this example */
}
}
class ControllerA: ControllerBDelegate {
let controllerB = ControllerB()
init() {
self.controllerB.delegate = self /* lets say we add here our delegate*/
self.controllerB.doSomething() /* tell your controller B to do something */
}
func somethingHappendInControllerB(value: String) {
print(value) /* should print "hey there, I'm a magician" */
}
}
I wrote the code from my mind and not testet it yet, but you should get the idea how to use such a pattern.

Using delegate between sibling view controllers in containers

I'm trying to make an app that uses three containers to show different content, but I'm having trouble communicating between the containers. I succeeded to use a segue to send some information at the tap of a button in one container to another container, but part of this information also has to be relayed to the third container. For this I wanted to use a delegate, but I cannot reference the right ViewController to the delegate variable.
So what I want goes as follows:
CollectionViewCell tapped, triggering segue to TableVC
TableVC receives information and updates the table
TableVC triggers delegate function in third VC
Third VC takes in some info and updates view
In the above I have managed to get 1 and 2 to work, but got stuck at 3.
I have made my protocol as follows:
protocol PurchaseDelegate {
func addToTotalAmount(product : Product)
}
In the TableVC I have declared var delegate : PurchaseDelegate? = nil and in the IBAction triggered from the segue: delegate?.addToTotalAmount(product)
In the third VC I have implemented the delegate as follows:
class thirdVC:UIViewController,PurchaseDelegate {
func addToTotalAmount(product : Product) {
println("Adding....")
}
}
All three containers are within a main VC that does some initial stuff in the application.
My problem is, that I don't know how to get a reference from thirdVC to my delegate variable in my tableVC.
Thanks in advance.
I ended up finding the solution to the problem after a bit further searching with inspiration from #Anna Dickinson.
Firstly, the containers must be ordered correctly in the storyboard. The container whose view controller implements the delegate protocol must be first in the list and then the other view controller further down.
Then, in the main view controller - the view controller for the view with the containers - the prepareForSegue function is implemented, since it will be triggered as the containers are initialized.
This all of the code remains as above, but the main view controller will be something like the following:
class MainViewController: UIViewController {
var actionVC : FirstViewController! // This is the one, that implements the delegate protocol
var tableVC : SecondViewController! // This is the one, that has a delegate variable
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "firstVC"){
self.actionVC = segue.destinationViewController as FirstViewController
} else if(segue.identifier == "secondVC"){
self.tableVC = segue.destinationViewController as SecondViewController
self.tableVC.delegate = self.actionVC
}
}
}
I'm not sure if the is the right, nor the best way to do this, but it works perfectly for what I need.

Resources