How to send message to grandparent View Controller in Swift - ios

I understand how to send data to a child View Controller when calling it with a segue. I think I also understand how to send data to a parent View Controller through the use of protocols and delegates. But how do I send data to a grandparent View Controller?
Options that I've considered:
Send the data to the parent and have the parent send it to the grandparent. But this seems like a lot of extra work when then parent doesn't need to know.
Use NSNotificationCenter. But (1) it is only the grandparent that needs to know, not the whole world, and (2) from the scant Swift documentation that I've found, NSNotificationCenter seems very unSwiftlike.

You can use protocols in this case too. I have done this way :
In the current controller(lets say grandchild controller), just intialize your grandparent controller and set delegate(same as you do in prepareForSegues in case of parent controller)
//use this lines when you want call the grandparent controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let grandParentVC= storyboard.instantiateViewControllerWithIdentifier("grandPVC") as! UIViewController
grandParentVC.delegate = self
//now call your delegate method here
As you specified, you know protocols (the links you included). Let me know if anything is unclear to you.
Hope this will work for you too!

Related

What is the difference between presenting a ViewController vs adding a ViewController as a child

I just read an interesting article that introduced me to a new concept that I have never heard before, which is adding a ViewController as a child of another ViewController. The article uses the UIActivityIndicatorView as an example to demonstrate a common use for when to use the addChild method and this got me wondering why would you use that method instead of just presenting the view controller.
The part I don't fully understand is the benefit of using that specific method since both presenting and adding as a child could be modular in a way that they can be reusable.
When would you use addChild instead of just presenting it? When does that make sense?
addChild:
let parent = UIViewController()
let child = MyViewController()
parent.view.addSubview(child.view)
parent.addChild(child)
child.didMove(toParent: parent)
Present:
let storyboard : UIStoryboard = UIStoryboard(name: "StoryboardName", bundle:nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "myViewController") as! MyViewController
self.present(myVC , animated: true, completion: nil)
The difference is that when you present a controller, it becomes modal, and when you add it as a child controller, it doesn't. The difference is in semantics and use cases.
Modal
As an example of modal window, imagine a window with an OK button in macOS that prevents you from interacting with the parent window until you close it.
Hence why there can be only one view controller presented by a particular controller: the presented controller becomes “main” for user interaction, and the user must close it to return to the parent window.
A typical example of a modal window is an alert.
Child
But a child view controller is a different story. There can be any amount of them on a particular VC.
The parent controller becomes sort of container for child view controllers. For example, there can be a dashboard with multiple panels, and each panel can be a separate controller. This allows to separate logic between different pieces of an application instead of cramming everything into a single controller, to implement user customization in easy way, etc.
A typical example of a parent-child controller interaction is UINavigationController: the navigation controller is the parent, and all view controllers pushed into it are its children. We don't make an uber controller with the whole application's logic, we separate it between different screens, and the navigation controller is only responsible for presentation.
It's very simple. addChild is when you want to embed the view controller in you parent view controller (you will also need to follow up with a view.addSubview(child.view)). present is when you want to, well, present it... think of it as a change of screens.
addChild:
present:

MVP: Can a view controller message a presenter of another view controller?

THere are 2 view controllers: master and detail view. They both have a presenter as I'm implementing the MVP pattern.
I need to update the data in the detail view controller.
I'm using this code in the master view controller.
detailVC.presenter?.set(data: presenter?.data[row])
I'm getting the data from the presenter of the master view controller and passing it to the presenter of the detailVC.
Is this good design?
If you create DetailVC before, you can use like that. Also u can use delegate patterns for that. MasterVC must have a delegate for DetailVC. Whenever you need to set your data you can use delegate.set(data: presenter?.data[row]) in your MasterVC class. But don't forget to set MasterVC delegate.
But if u want to create DetailVC and set parameters, u can use init method. Create an init function for your detailVC with the required parameters like that.. Write that function into your DetailVC class or create a DetailViewControllerInit class for your custom init functions with different parameters..
static func initDetailVC(data: DataType?) -> UIViewController {
let vc = UIStoryboard.... // create DetailVC here..
let presenter = viewController.presenter
presenter.set(data) // or presenter.data = data
return vc
}
After that u can create DetailVC in MasterVC like that
let detailVC = DetailVC.initDetailVC(data: presenter.data[row])
Though your implementation would certainly work, here are a couple of things to consider in order to improve it:
1) It hinges upon the master view controller knowing about a lot of different things (the detail view controller, the presenter, and the presenter's API). This can create coupling, which can make it more difficult to later refactor your code. Instead of calling detailVC.presenter?.someMethod(), I would consider adding a pass through method to detailVC that handles the calling of the presenter method (as well as anything else it needs to do at the same time) so that your architecture will be more modular and it will be easier to later swap out components.
2) I would consider decoupling the view and the model. Since MVP is really M<->P<->V in practice, ideally your model and view would not really communicate with or know about each other. Here, it seems like the view, or at least the object in which this line of code lives, knows about model when you call detailVC.presenter?.set(data: presenter?.data[row]). In order to do this decoupling, you could just have the view send an event / message prompting the presenter to do its thing, rather than manipulate the data directly. For example: detailVC.presenter?.newInputReceived(input: "hey!")
Hopefully that helps!

iOS object or delegate between two controllers?

Evening, my question is full about theory.
I understood reading from Apple developer documentation that is better to use the Delegates Pattern to keep track of some object attributes. In this way we can access the delegate without access to the object. (I really didn't get the reason of this choice)
I also understood that is better to define: protocolDelegate: class
and when we are declaring the delegate inside the class it's better to use the weak word to prevent some "kind of problem cycle". (??)
So, while I was playing a bit with code, I've discovered that you can't pass a weak delegate between two view controllers, because of course, when you change the controller, the weak delegate is going to be deleted because is a weak thing (or at least this is what I understood).
So, I have to choose between 2 options:
make the delegate "strong" deleting the weak key.
or pass the object in the segue and keep the delegate as weak.
I have a lot of confusion, can you clear my mind? :D
The cycle you're referring to is called a retain cycle.
Let's use a concrete example to clear this up: say you've got a UIViewController which has a UITableView. The view controller has a strong reference to the table view. The view controller now wants to act as the delegate to the table view.
Now, if the table view would have a strong reference to its delegate, we would have the following situation: the view controller has a strong reference to the table view, and the table view in turn would have a strong reference back to the view controller. Thus neither can ever get deallocated.
To break this cycle, references to delegates are usually weak. This allows the retain count of the view controller to drop to 0 eventually, which can in turn release the table view.
Your classes that want to use delegates should also follow this pattern and use weak references to their delegates. You should thus pass the required references via your segue.
I will concentrate on the first part of your question, since the previous answers have covered the rest pretty well.
Consider the following situation: you have a class that handles some kind of network connection - it sends a request to a server and gets a response. Outside of this class there is a viewController that has a button that triggers the request and a view which presents the response to the user.
Basically, the network handling class should be able to get some message from the viewController (button pressed) on one hand and pass the viewController the response on the other. So there should be bidirectional communication between the two classes. While the passing of the buttonPressed message to the network handling class is pretty obvious, the reverse part (passing the response) is a bit more tricky, because the network handling class should not be aware of who created it and who calls it (good OO practices and memory leaks prevention).
That's the point where the delegate pattern comes in. It allows an object to pass data to whoever is interested in it without knowing anything about the recipient. The class that passes the response only knows some 'delegate' and not another class. In addition you can take out the network handling class as is and put it in another project. Because it isn't supposed to know any other class from its original project, only some 'delegate', it can be put into another project without any modifications.
I hope it can help you to get the reason of the choice.
I think pass the object with segue, Segues are a very important part of using Storyboards in Xcode. We can go over the different types of seguesanother time, but this one shows you how to use the “Show” segue, as well as how to pass data between the two view controllers to customize the second one with whatever custom data is required.
You can easily use segues example; Under below you can send currentstring to destinationViewController inside sentstring , also ShowSegue is your segue identifier
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowSegue" {
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.sentstring = currentstring
}
}
}
Navigation between viewcontrollers maintain stack of viewcontrollers.
For example aVC is firstviewcontroller then top of stack will be aVC,
now when you push or show another viewcontroller say bVC then now top of statck is bVC. So stack looks like,
aVC -> bVC(top)
now you push another cVC then,
aVC -> bVC -> cVC(top).
So top of stack is always visible to user.
at current situation, aVC and bVC and cVC are not deallocate. they are in memory. But if you pop or dismiss cVC, then it will deallocate from memory and now your top of stack looka like,
aVC -> bVC(top).
So viewcontrollers live in stack till they are not popped or removed. So, they are strog reference by default.
Segue is nothing but you can say that they are graphical representation of push or pop operation.
another thing is that delegate should be weak that because it can create retain cycle if they are strong.
you can called delegate as representative in general sense.
Now, if you are using segue, send your object in prepareForsegue and it will manage everything else.

How can I access an IBOutlet in ViewController from another class?

I have a class that is a GCDAsyncUdpSocketDelegate. Now I'm getting some data thru UDP (in a background thread) and need to display that data with an IBOutlet in my ViewController. But ViewController is just a class. Where is its object instance defined? What is its name? What do I do to access it, or one of its properties, methods? I've seen this UIApplication.sharedApplication().keyWindow?.rootViewController method but this doesn't guarantee the exact ViewController I want to access, I guess.
I don't know the exact structure of your app, but you could try something like this:
let rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
if let customViewController = rootViewController as? CustomViewController {
customViewController.label.text = theData
}
else{
let customViewController = storyoboard!.instantiateViewControllerWithIdentifier("CustomViewControllerID")
// (This assumes CustomViewController is defined in the same
// storyboard as the view controller running this code. Otherwise,
// you need to get a reference to the storyboard first).
rootViewController.presentViewController(customViewController, animated:true)
customViewController.label.text = theData
}
EDIT: If the object that receives your asynchronous server data is dettached from your navigation (good call, by the way), you can have it:
Store the data somewhere it will persist even if said object is deallocated
(or make said object a singleton, if applicable).
Post a system notification (using NSNotificationCenter) once the data is available, and have your main ViewController "listen to it" (-addObserver:selector:name:object). When the notification is posted and the notification handler is called, your view controller can retrieve the data from its (persistent) location (file, or property of the singleton mentioned above if you chose that route).
Finally, for the case when the data is already available by the time your ViewController is instantiated, check for data availability and retrieve it if present in e.g. your main view controller's viewDidLoad().
There are many ways you can achieve this,
1. Use Protocols or Delegates
You said "I have a class that is a GCDAsyncUdpSocketDelegate." Write a protocol implementation in that class and make your view controller subscribe to that delegate, when you get delegate call back for your GCDAsyncUdpSocketDelegate do neccessary check and fire your custom delegate to the view controller, pass the data as an argument to the delegate method. In the view controller get the data and update the IBOutlet.
2. Use NSNotificationCenter (dirty way of doing things, gotta be careful though)
In your view controller class add an observer for the notification you are going to post like,
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateUI:", name: "didRecieveDataFromUDP", object: nil)
Add a method in your VC
func updateUI(notifObject:NSNotification){
let responseString = notifObject.object
self.yourOutlet.string = responseString
}
and also remove the notification observer when you are done with the VC.
In the GCDAsyncUdpSocketDelegate delegate class when you get the call back with results fire this notification,
NSNotificationCenter.defaultCenter().postNotificationName("didRecieveDataFromUDP"", object: theResposenObjectYouNeedToSend)
3. Use sharedInstances or mis-use the AppDelegate (not recommended)
Create shared instances or singleton classes that will live through the app lifecycle and use them to store/retrieve data from any class or any thread.
Hope this helps, I wish you choose the first or second way.

Is prepareForSegue right way of passing value between view controllers

I'm trying to learn Swift and I'm trying to develop the famous note application.
There is an array bound to a tableview and another view for adding notes.
At second view textfieldshouldreturn event triggers a segue and goes back to tableview.
I wanted to learn if this is the right way. Because by doing this way I'm manipulating a variable in another view controller. I'm not a MVC master but I felt like it is wrong. Here is my code snippet:
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.performSegueWithIdentifier("backSegue", sender: self)
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "backSegue"){
let navController = segue.destinationViewController as UINavigationController;
let myController = navController.topViewController as NotesTableViewController;
if(self.ourTextField?.text != nil || self.ourTextField?.text != ""){
myController.notes.append(self.ourTextField?.text ?? "");
}
}
}
Thank you.
Your question is not really about prepareForSegue but the relationship between view controllers. The reason that your design "feels wrong" is that it is. The problem is that your note writing view controller knows too much about the view controller that is using it because it is directly manipulating a variable from the calling view controller. In order to directly manipulate the variable, it must know the class of the caller.
Why is this a problem? It makes your note writing view controller less reusable. If you write the note writing view controller correctly, then you could reuse it in other apps. To make it reusable, you need to decouple the note writing view controller from the caller - it must not know who exactly is calling it.
So the question becomes, how do I pass data back to the caller if I don't know who called me? The answer is delegation.
Delegation works like this:
You create a protocol which describes a method or methods that the implementor of that protocol will implement. In your case, you could use a protocol like NoteWriterDelegate that implements the method takeNote(note: String).
protocol NoteWriterDelegate {
func takeNote(note: String)
}
Define this in the file along with your note writing view controller.
Your note writer will have an optional pointer to the delegate:
weak var delegate: NoteWriterDelegate?
You need to declare your first view controller as a NoteWriterDelegate:
class ViewController: UITableViewController, NoteWriterDelegate
And then implement the required method in your first view controller:
func takeNote(note: String) {
notes.append(note)
}
When you call prepareForSegue in preparation for moving to the note writing view controller, you pass yourself as the delegate:
destinationViewController.delegate = self
In the note writing view controller, when you have a note to pass back to the caller, you call takeNote on the delegate:
delegate?.takeNote(self.ourTextField?.text ?? "")
By doing it this way, your note writer only knows that it is talking to a NoteWriterDelegate. If you want to reuse this in the future, you just drop your note writer class into another project, implement the delegate, and it works without you having to touch the code in the note writer class.
I would recommend passing data via prepareForSegue in most cases. It's pretty simple to set up and easy to understand.
However, I would recommend never updating UI elements (labels, text fields, etc.) on the destination view directly. In my opinion, this is bad coupling that creates a lot of problems.
Instead, create a property or properties on the destination view controller that the caller can set in prepareForSegue to pass data to it. These should be special purpose properties used exclusively for passing data. The destination view controller is then in charge of using the data in these properties to update its UI or internal state.
Delegation is a valid approach, but I find it to be overkill for most situations. It requires more setup and is more abstract. This abstraction isn't needed in a lot of view controller relationships. If you discover you need to reuse a view controller, you can always refactor to use delegation later.
I do not believe that the prepareSegue is the ideal way for passing data between view controllers...at least not directly.
I share your concerns about using prepareForSegue to pass values between view controllers. The source view controller shouldn’t know anything about the destination view controller (and the other way around, for that matter). Ideally view controllers should be separate islands with no visibility into one another.
To address the coupling that storyboards seem to encourage, I’ve often used some form of the mediator pattern to pass data between view controllers. Here is a pretty good blog post on how to implement a version of this pattern around storyboards: http://coding.tabasoft.it/ios/mediator-pattern-in-swift/ . As always, this pattern may not be the best fit for all situations, but I feel it has been a good solution in a lot of my past projects.
Basically, how the mediator pattern would work within the storyboard paradigm is that in each view controller’s prepareForSegue method, the the segue object is passed to the mediator object. The view controller doesn’t care what’s inside or where the navigation is going next; it just knows it’s about to not be visible. The mediator, which has just been passed the segue object (containing the source and destination view controllers), is then responsible for passing data between the source and destination view controllers.
Using this pattern, each view controller is blissfully unaware of the existence of the other. The mediator class, on the other hand, must know about the relationships between the view controllers (and the view controllers' interfaces) in the navigation path. Obviously if the navigation changes, or the view controllers themselves change, the mediator class will need to adjust. Each view controller, however, need not have any dependence on each other, and therefore need not be updated to to accommodate changes in the navigation path or changes to the other view controllers along that navigation path.
It is not 'the' right way, but it is a right way. Especially in storyboard applications.
Here is an alternative way of passing value and calling the view.
var myNewVC = NewViewController()
myNewVC.data = self
navigationController?.presentViewController(myNewVC, animated: true, completion: nil)

Resources