Passing data between two view controllers seems to have been solved using delegates. My situation is little different and since I am new I don't know if I can solve this with delegates.
I have 3 view controllers. GrandParent, Parent and Child.
GrandParent instantiates Parent that shows list of CategoryGroups.
Clicking on a CategoryGroup instantiates Child View Controller that shows list of Categories.
I want that when user clicks on any Category, GrandParent gets to know the Category being clicked.
What I have now?
On Child.h view controller
#protocol CategorySelectDelegate<NSObject>
- (void) categorySelected:(CategoryModel *) categoryModel;
#end
On Child.m view controller
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"selected category:%#", _categories[(NSUInteger) indexPath.row]);
[self.delegate categorySelected:_categories[(NSUInteger) indexPath.row]];
[self dismissViewControllerAnimated:YES completion:nil];
}
On GrandParent.h
#interface GrandParent : UIViewController<CategorySelectDelegate>
On GrandParent.m
- (void)viewDidLoad {
[super viewDidLoad];
ChildViewController *categoryViewController = [[ChildViewController alloc] init];
childViewController.delegate = self;
}
- (void)categorySelected:(CategoryModel *)categoryModel {
_categoryLabel.text = categoryModel.name;
NSLog(#"categoryLabel:%#", _categoryLabel.text);
}
But I know this is incorrect since GrandParent is not the one instantiating Child directly, its is always parent who gives birth to Child
Question
- How can I pass the categoryModel from Child to GrandParent?
- In general, how can I pass a data from one Child Controller back to any Ancestor Controller?
UPDATE
For now, I have added 2 delegates to solve this problem
a.) 1 delegate from Child to Parent
b.) 1 delegate from Parent to GrandParent
This works but I don't think it is a good design is data needs to be passed between 2 or more view controllers since one will end up creating new delegates to pass values.
I had more or less same use case, and i preferred to go with the notification, as its seems to be loose coupled object,
making delegate just for exchanging data would not be a good choice.
Please refer How Best to Use Delegates and Notifications which says,
Notifications result in loose coupling between objects. The coupling is loose because the object sending a notification doesn't know what is listening to the notification. This loose coupling can be extremely powerful because multiple objects can all register to listen to the same notification
So down the line, if some other view controller or any other widgets wants to handle data, it can be achieved easily without setting one more delegate.
but this line also holds good
The fact that notifications and delegates provide such different coupling are an indicator that they should be used in different scenarios. If table view used notifications instead of a delegate, then all classes that use a table view could choose different method names for each notification. This would make it hard to understand code as you would need to go and find the notification registration to work out which method is called. With a delegate, it's obvious: all classes that use a table view are enforced to be structured in the same manner.
Interesting problem you have.
You can establish a set of global protocols that can be subscribed to by any object and pass around who receives the messages. This can be as easy as building a separate .h
So, as the parent builds the child, the parent must set the grandparent.delegate = child before presenting that child view controller.
And then of course as the child is removed and the parent shown again, the delegate needs to be set back.
If you want to use delegate then there is no way but propagating the GrandParent to Child as a delegate so that it can send callback to GrandParent when category is selected.
Alternatively you can post NSNotification from child when category is selected and add GrandParent as a observer to get the notification.
Related
I have a split view controller in which either side has table views and needs table data reloading every time some interaction happens on either side. I have implemented delegate to update my detail view controller whenever a cell is selected on left side (master) of split view controller.
1.I wish to know do I need to implement another delegate to make it happen both ways (i.e. updating master view when a )or is there any generic approach.
2.I have already written code for both classes, so what is happening is that when i select a cell on left , right updates via a delegate method reloading/refreshing the view BUT the methods like viewWillAppear/viewDidAppear/viewWillDisapper ...etc are not called. I am now manually calling viewWillAppear method from the delegate Method that's triggered on left cell selection. Is there a technique i am missing so that the class methods are called automatically. or Can anyone point to the best approach to use a splitViewController?
First, it may not be a good idea to call viewWillAppear, etc from your code because those behaviors could change in the future. (e.g., viewDidLoad used to be called multiple times in the early iOS versions, now it is called once per instance). You could just move your code into a separate method.
Second, you may want to look at NSNotification as a way to communicate the changes. It's easy and doesn't require you to keep any delegate pointers around.
For example you might add to the child view controller .h:
#define MASTER_UPDATED #"MasterUpdated"
#define DETAIL_UPDATED #"DetailUpdated"
and then in the master controller something like:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateFromDetail:) name:DETAIL_UPDATED object:nil];
...
-(void)updateFromDetail:(NSNotification *)no
{
NSDictionary *nd = [no userInfo]; // get relevant information
// take action....
}
From the detail side, when an item is tapped, you'd send a message like:
NSDictionary *userInfo = #{#"somekey":#"somevalue", #"anotherkey":#"anothervalue"};
[[NSNotificationCenter defaultCenter] postNotificationName:DETAIL_UPDATED
object:self userInfo:userInfo];
You probably need to use one of the reload... methods of UITableView to reload the table. The reloadData method will reload the complete table. If you know which rows are changed, then it is probably better to use one of the other methods.
If the changes are also in the number of row, then you'll need to use one of the insert... or delete... methods to get proper animations.
See the UITableView documentation for all the details.
For your first question, I would expect the detail view controller to have the master as a delegate. But the master should simply know which detail view controller is on the right. After all, he has started it.
Hi #Divjyot I am now working on similar scenario but I have to change to a different viewcontrolelr in detailVIewController(SecondaryViewController) on clicking a cell, so this is what I did
I created and array with all viewcontrollers in masterViewController(PrimaryViewCOntroller) and passed to the detailViewController on cell selection using a delegate. So on clicking the cell in primaryViewControlelr updates the secondaryViewCOntroller with a new ViewController. If u want more info abt how to implement this comment below
Asnwering to your questions:
1º The protocol UISplitViewControllerDelegate has a method which said when splitviewcontroller is going to change the display mode. This method is very useful to update data of some view controller.
- (void)splitViewController:(UISplitViewController *)svc willChangeToDisplayMode:(UISplitViewControllerDisplayMode)displayMode;
2º For other hand, you can need to update the data of view controller when user selects a cell or any other actions.
If you share more information about your code I can be more specific.
In UINavigationViewController, if I wanna pass values from one controller to next, just call - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender , but what should I do if I wanna pass values from one viewController to previous viewController
I remember running across this same issue a few projects back. I can't find the my code to answer this question, but I did find a few tutorials.
http://prateekvjoshi.com/2014/02/16/ios-app-passing-data-between-view-controllers/
http://www.infragistics.com/community/blogs/torrey-betts/archive/2014/05/29/passing-data-between-view-controllers-ios-obj-c.aspx
and hence the concept of delegate came forth from segues.
Basically Segues are transition from one view to another but the child view is over the parent view, (inside a stack) so the parent view is still loaded.
So if u put segues everywhere and pass values between them then objects will keep on be creating and stored inside a stack and thus the cycle carries on.
So delegates was introduced.
Delegate is a method by which a child view controller(the later one) sends information using the inbuild delegate methods or self created protocol methods to the Previous view controller(the first one).
Here the one sending the information(later view) declares a delegate object, and a delegate method.
Which is then implemented by the recieving class(first view). So even after the later view is popped from the stack, the information is sent back to the root view by the delegate method.
Go through the documentation, its given in a more appropriate way
Hope this helps
Set previous view controller as delegate of current view controller and pass any values you want. This is standard approach.
Good morning.
I have been working through a lot of tutorials the past year, and have seen several methods of passing reference back up the View Hierarchy with Storyboards. I was wondering what some best practices are, and more importantly any pitfalls using the following methods:
Protocol - the child view implements a protocol, that the parent view sets itself as the delegate to and responds to messages. This can be used to pass back information to the delegate. The Child does not need to know anything about the reason it was called, it does it's job, and sends back whatever information it was asked for.
Public property of the Child ViewController that has the model reference. On the segue you retrieve the reference to the destination view Controller. This view controller has the model property exposed publically. You pass in your reference to the model. I personally like this method - can't see any real pitpalls with it. then when the child makes changes the model is changed directly. at the point [self.navigationController dismissViewControllerAnimated:YES] gets called to or however you navigate off/dismiss the view controller you have the information in the model you want.
ANYTHING ELSE? - someone else has a good idea, i'd love to hear it.
Thanks for anyone's input on this. I know there are always more than 1 way to skin a cat, I just want to skin the cat cleanly and quickly. (-Sorry cat)
Steve
For the sake of completeness, in addition to the two options you enumerate, there are a couple of other options:
Use unwind segue (iOS 6 and later), and you can pass data to the "destination" (the controller you're unwinding to) via prepareForSegue;
Use singleton for your master model object;
Use Core Data (or SQLite) for persistent storage; or
Use notifications or key value observation (though this is much better for communicating data updates to multiple objects).
Personally, I'll generally use a protocol when it's just a matter of a presented view controller wanting to inform the presenting view controller of some information, but each of these techniques have their respective uses.
Depending on your need there is another option and no segue is required.
The child view controller could create its own object or obtain a known shared reference of any object. Creating its own object would be good for adding a new item to a table. Obtaining a known shared reference would be good for something like a settings object.
When the child view controller is ready to be dismissed it posts a notification.
[[NSNotificationCenter defaultCenter] postName:MyAppDidCreateNewItem object:self.item];
or
[[NSNotificationCenter defaultCenter] postName:MyAppDidUpdateSettings object:self.settings];
The parent view controller registers as an observer to the notification. When the notification happens, the parent view controller can use notification.object to get the reference and update itself.
I'm currently refactoring my app to be sure the it's MVC compliant.
I would like to split the controller (MyController which extends UIController) and the view (HomeView which extends UIView) I set the view in myController using
self.view = [[HomeView alloc] init];
When I push an UIButton, a method is called in the view, and in this method I would like to call a method from the controller.
In my view
[zenModeBtn addTarget:self action:#selector(touchZenMode:) forControlEvents:UIControlEventTouchDown];
...
- (void) touchZenMode:(id) sender {
[myController playZenMode];
}
But having a reference to the controller in the view is really a bad practice isn't it ?
EDIT :
So in my UIViewController I've made this :
- (id) init {
HomeView* myHomeView = [[HomeView alloc] init];
[myHomeView.arcadeModeBtn addTarget:self action:#selector(touchArcadeMode) forControlEvents:UIControlEventTouchUpInside];
self.view = myHomeView;
return self;
}
is that correct ?
The view talking to your controller is no problem, as outlined by some answers here. E.g. a text field can notify its controller via the defined delegate methods.
However, your design is still seriously flawed. Your view has absolutely no business handling a button press itself. Your intuition that the view should not know about its controller is correct.
Your controller should know about the button and how to react to it being tapped. That's why a controller has button IBOutlets to tell the button to e.g. change its title or enabled state. And it has button handlers to react to UI events. It is the controller's job to handle this logic. It is the view's job to display the title, gray out or send a tap event back to the controller.
The only code you should put into a view is basically how to draw itself. Everything that cannot be handled by a controller.
The basic idea of the MVC pattern, as used in Cocoa Touch:
As described here: The Model-View-Controller Design Pattern
What you want to achieve, is a form of loose-, even blind maybe, coupling. By using protocols (for delegation mechanism), a View only knows that there is an object that adopts a specific protocol, it can 'talk' to.
Take the UITableView for instance. It does not need to know that there is a certain type of UIViewController that helps it gather data, but only that there is an object that adopts the UITableViewDatasourceDelegate and/or UITableViewDelegate; that object can be of any type.
In your edit, you use the target-action mechanism, which is another way of achieving loose-coupling. You set up the connection at runtime; your View does not know your Controller. Therefor: correct, apart from the comment #Mundi made about your init implementation being incomplete.
The view needs some way to communicate things back to the controller, ask it questions about what to do next, etc. So it's perfectly fine for the view to know something about the controller.
Some of the built-in views, like UITextField, define protocols they use to tell their delegate about what's going on, or ask it to do something. You typically implement the protocol in your controller. That way the view doesn't really know much about the controller, just enough to communicate. That makes your view more generic and reusable.
What you want to avoid is for your view to have direct links to your model. The role of the controller is to mediate between the view and the model. You should be able to completely change how the view is implemented without touching the model, and vice-versa.
You can put that method in a protocol in your view's interface:
#protocol MyViewDelegateProtocol <NSObject>
-(void)myMethod;
#end
you put a new property of NSObject type called delegate in your view.
you make your view controller comply to that protocol and when it inits the view assign the delegate property to self.
you implement myMethod in your view controller implementation.
and now you just call [delegate myMethod] from your view whenever you need it.
I implemented a custom UIViewController which has multiple child view controllers (mainly by using the storyboard's Container View but not only).
I'm wondering what is the best way for the "root" view controller to send a message to its children, taking into account that the child(s) view controller(s) concerned by the message sent is not necessarily a direct child of the "root" view controller ?
For example:
I want to send a message from #0 to view controllers #1 and #4. The naïve implementation is to iterate over the child of #0 and send them the message, like so:
for (UIViewController *childVC in self.childViewControllers)
{
if ([childVC respondsToSelector:#selector(myMessage:)])
{
[childVC performSelector#selector(myMessage:)];
}
}
But it simply doesn't work, because #3 will receive the message (or probably not if it is a UINavigationController and I'm calling a custom method) and don't propagate it to its children (here #4).
So is there a possibility to send a message to one's children, and let the message propagate through the UIViewController hierarchy ?
If not, an alternative would be to use the NSNotificationCenter, make the children interested in the message (#1 and #4) observe for a notification, and make the root view controller post a notification when necessary. But I find it a bit of an overkill only to dispatch a message to two children.
I tend to avoid NSNotificationCenter, because if one is not careful it will create a bit of Spaghetti Code. Still, in this case I would use it, because it's even more of a overkill to taking of the logic of how the message should propagate between the childsViewControllers and possibility the ochildsViewControllers's childs.
I think going through NSNotificationCenter is the best way to go. Each child view controller can decide for themselves what notifications they want to respond to; it takes the responsibility of filtering away from the object posting the notification.
NSNotificationCenter
And its Guide
You could also do something like this, but I'm not saying its a good idea:
#interface UIViewController (ReverseResponder)
- (void)reverseRespond:(SEL)selector withObject:(id)object;
#end
#implementation UIViewController (ReverseResponder)
- (void)reverseRespond:(SEL)selector withObject:(id)object
{
if ([self respondsToSelector:selector]) {
[self performSelector:selector withObject:object];
}
for (UIViewController* childViewController in self.childViewControllers) {
[childViewController reverseRespond:selector withObject:object];
}
}
You can access all of your children with self.childViewControllers, and pass a specific index to get to a particular child. To get to 4, you'll have to access 3's topViewController.
UIViewController *child4 = [(UINavigationController *)self.childViewControllers[2] topViewController];
Whether this is a good way to do this, depends on what you're trying to do. Why are you sending messages down to this level?