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

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!

Related

Getting data from another view controller (not via passing the data forward or backward) in Swift 4

Say I have View Controller A, where I have an array with data.
Now I also have a couple of other View Controllers (B,C,D, etc...)where I need that array.
I know I could pass the data forward using segues to each of the other View Controllers. But It seems not the perfect solution for me as I have to pass the data each time through a lot of different View Controllers
I would rather have a method where I define in View controller B "Get the data from View Controller A"
(As I understand passing data backwards is not what I want to achieve because I don't want to change the array in View controller B and pass it back to View Controller A. I only want to read/get the data within View Controller B from View Controller A.
Can somebody point me to a solution for this? Or do I have to pass the data forward from VC A to VC B?
I guess it is a matter of taste but I would rather write my code in VC B/C/D in case I need the data from VC A than passing each time data from A to every other VC...
Create a singleton class. let's say DataManager.
class DataManager: NSObject {
// A Singleton instance
static let shared = DataManager()
// your array
var dataArray = [String]()
}
now access this array from anywhere in the app.
Print(DataManager.shared.dataArray)
Do I have to pass the data forward from VC A to VC B?
You don't have to, but you should.
The suggested singleton pattern is considered an anti-pattern nowadays, you can read more about why here: What is so bad about singletons?.
It's not just a matter of taste, your code will be more maintainable if you avoid singletons.
Even if you use CoreData instead of an array in ViewController A, you will/should pass references from A to B, be it the Managed Object Context or an array of objects that you retrieved from CoreData in ViewController A. You can see this happening in the default template provided by XCode/Apple for a MasterDetail app with CoreData (just create a new project for that).

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 to send message to grandparent View Controller in Swift

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!

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)

How to push to one view controller object from multiple view controllers

So I have a view controller FavoritesViewController and I have an instance of that view controller:
FavoritesViewController *FVC=[[FavoritesViewController alloc]init];
If I have two other view controllers, HomeViewController and SettingsViewController how do I have it so that I can push to that one particular instance "FVC" from both view controllers. I guess the real question is how/where can I initialize that instance "FVC" so that it is recognized by both view controllers and don't initialize it in HomeViewController or SettingsViewController.
Thanks
When you look at the MVC pattern, the object that you should really care about sharing is the model, not the controller. Your questions suggests to me that maybe the ViewController is also performing the responsibility of being the model.
If this is true, you might want to create another class (called Favorites, perhaps) and follow the suggestions in some of the previous answers to make it a singleton, if necessary. Doing it this way, rather than making the VC a singleton also has the benefit of working the same way whether you are using storyboards, xib, or code.
Maybe you have a good reason to need to share the VC itself, but I thought it would be worthwhile to question that premise.
If you want a single instance of your favorites view controller that is shared throughout your app, make it a singleton. Do a Google search on the singleton design pattern in iOS. The idea is that you would add a class method sharedFavoritesController that would always return the same instance, and use that.
The class method would look something like this:
+(FavoritesViewController *) sharedFavoritesVC;
{
static FavoritesViewController *_sharedFavoritesVC;
if (! _sharedFavoritesVC)
_sharedFavoritesVC = [[FavoritesViewController alloc] init;
return _sharedFavoritesVC;
}
Then #import the header for your FavoritesViewController class, and any time you need to invoke it, use:
[FavoritesViewController sharedFavoritesVC] to get a pointer to it.
Way 1 :
Declare
extern FavoritesViewController *FVC;
Way 2 :
If this is your rootViewController then in other viewcontrollers
FavoritesViewController *fvc=(FavoritesViewController*)appDelegateObj.rootViewController;
Way 3 :
use singleton
+(FavoritesViewController *) sharedInstance;
{
static FavoritesViewController *SVC;
if (! SVC)
SVC = [[FavoritesViewController alloc] init;
return SVC;
}
You could wrap a singleton pattern around that view controller.
Or, less elegant but more common, instantiate it in you AppDelegate and fetch it from there.
Be aware that the same instance of a view controller can only be once in the stack of view controllers. Plus - in principle - there is nothing wrong with having multiple instances of the same view controller class.
Edit in reply to your comment:
Not that I recommend that but this is how you would fetch a property sharedFavoritesViewController (which may refer to an instance of your FavoritesViewController) when your app delegate class is named MyAppDelegate:
FavoritesViewController localVar = [(MyAppDelegate)[[UIApplication sharedApplication] delegate] sharedFavoritesViewController];

Resources