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.
Related
I have a navigation controller with 4 view controllers on the stack. I need to access a function in ViewController1 from ViewController4. What is the proper way to do this?
Do I pass the reference to ViewController1 through ViewController2 & 3 then access the function using a protocol delegate?
Do I store a reference to ViewController1 in a struct then access it from there?
Number 2 is what I am currently doing. I set the reference when I leave ViewController1 then set that reference back to nil when I'm done with it.
Why I need to do this:
VC1 has a tableview with a bunch of items. The data in these items is edited in VC4. Once editing is done in VC4 I want to save, pop to root, and reload the tableview with the new data.
Your plan is all wrong. What you should be doing is have a data model that can post notifications about changes in its data. There should be no link whatsoever between the view controllers. VC1 should be prepared to listen for notifications from the data model. When VC4 updates the data model, the data model will tell anyone that is listening that it has been updated.
With this setup, any number of view controller can all be listening to the same instance of the data model being passed around. Any part of your app can respond as needed to these notifications. The best part of this design is that no class has any knowledge of any other specific class except everyone knows about the data model.
The data model has no knowledge of any controllers or views.
No controllers have any direct link to other controllers except for one that needs to present another.
I think you can use notification. Make VC1 to subscribe the notification. When need, in VC4, send the notification.
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.
This might sound like duplicate, but I need farther understanding and explanation about my Scenario please.
In my app, I have one InfoViewController, which represents information when called (pushed)from other Controller like, 1)HomeViewController, 2)FavouriteViewController and 3)DownloadViewController. All 3 held by UITabViewController
InfoViewController had about 10 buttons and corresponding actions. I use separate singleton to hold all info objects.
All 3 ViewController(Held by TabVC)--> Loads object to Singleton --> InfoViewController uses that to Present Detail Info
HomeViewController - Its for new 'Info' to present,which changes everyday
When Information is present,
user can mark it as a Favorite, which then Listed(In UITableView) on FavoriteViewController.
User can save it in phone for future reference, which will be listed(In UITableView) on DownloadViewController.
On selection of cell all 3 represents details using InfoViewController.
Now I want to present only one instance on InfoViewController to be visible to user. Not all in all 3 tab. Currently I am switching it back to Main screen with ViewDidDisappear method. Which works only when I add InfoViewController as child to main 3 VC. Not by push.
Now my problem is, i tried to use Appdelegate to initialized SharedInfo Object
sharedInfo = [InfoViewController alloc]init], but it goes to black screen. I have to initialize it as
[self.storyboard instantiateViewControllerWithIdentifier:#"InfoViewController"];
but this is not allowed in AppDelegate or making shared instance on InfoViewController itself.
How do I achieve Only one Instance present to user at a time??
I think rephrasing what you need might help. I don’t think you really want a shared instance of the UIViewController. What you want is a single source of the data which is displayed in one or more views and a method to update that data in all the views when the data changes.
Two possible solutions are:
NSNotification
Any view which contains the shared data subscribes to notifications when the data is changed. When data is updated via the singleton it sends a broadcast to anyone listening to update the view data.
View Lifecycle
In this second method the data in a particular view is only updated when the user brings up that view. Note in a tab bar interface that the viewDidLoad is called when the view is initially loaded. DO NOT update your data here if it changes. Instead you want to update the data in viewWillAppear as this is called every time you navigate to that view. In viewWillAppear you have code that checks the data and updates it if needed.
There are other ways to skin this cat, but either of the two above should work for you.
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.
I am implementing a custom URL scheme which will add entities to my data model. The details for the entity are contained in the URL. The basic idea is that an email link (or a link from another app) will open my app and add the new entity.
The problem is, I can never be sure which state my app will be in when responding. Any number of view controllers might be in view. If the list of entities is in view, I need to insert a new row for that entity. If other views are on screen, I need to react differently. Some views might also be modal.
I would be satisfied with a simple pattern when this happens - abort whatever the user is currently doing, and pop to the root view controller. From here I will probably push to a controller where I will show the new entity being added.
I experimented with always dismissing any modal displayed and popping to the root, with the benefit of not needing to know what exactly is being displayed:
[(UINavigationController *)self.window.rootViewController dismissViewControllerAnimated:NO completion:nil];
[(UINavigationController *)self.window.rootViewController popToRootViewControllerAnimated:NO];
This works reasonably well, but there at least two cases where it is insufficient:
If some object was created when the modal was presented (the modal is then used to modify the new object), and it is the delegate's responsibility to delete the object if the user cancels, the entity will remain alive.
If a UIActionSheet is being displayed, all bets are off. I can't dismiss this without knowing the controller that displayed it, having access to that controller, and sending it a message to dismiss the action sheet. Without doing so, the root view controller is popped to but the action sheet stays on screen. Subsequent taps on the action sheet of course cause a crash, since the controller that displayed it is gone.
How might I handle this robustly? Should I be trying to find out specifically which view controller is currently presented, and handling each scenario in turn? Or is there a more scalable solution that won't need updating each time I add controllers or change my application's flow?
It sounds like you are trying to do several things:
When the user clicks on your custom url, you want to add an "entity" to your model.
You want to display this new entity in some sort of EntityListViewController, which may or may not be on the ViewController stack.
You (may) want to pop off all view controllers above the EntityListViewController.
You want the user to know there was a new entity added (perhaps just by doing item 2).
You want to push some kind of EntityViewController, or if there is currently an EntityViewController in the view controller stack, you want to reload with the new entity's data.
It sounds like you have item 1 ready to go, since you didn't explicitly ask about handling the url click and inserting the new model object.
For the rest, a flexible and MVC-ish pattern would be to use NSNotificationCenter.
The code that inserts the new model object would "post" a notification:
[[NSNotifcationCenter defaultCenter] postNotificationName:#"entity_added" object:myNewEntity];
Then your various UI elements (e.g., UIAlertView and UIViewController subclasses) would listen for this notification and take some useful action (like closing themselves, or in the case of EntityListViewController or EntityViewController, reloading themselves).
For example, a UIViewController subclass might do this:
-(void) viewDidLoad
{
[super viewDidLoad];
[[NSNoticationCenter defaultCenter] addObserver:self selector:#selector(onNewEntity:) name:#"entity_added" object:nil];
-(void) onNewEntity:(MyEntity*)entity
{
// close, or redraw or...
}
-(void) dealloc
{
[[NSNoticationCenter defaultCenter] removeObserver:self];
// if not using ARC, also call [super dealloc];
}
To keep your life simple (and not worry too much about all the different UI states), I would consider doing this when the notification occurs:
Have the EntityListViewController redraw itself (does not matter if there something on top of it).
Show some sort of short-lived indicator in the nav bar (or somewhere else you know is always visible), or play a sound so the user knows that an entity was added.
And that's all.
If you take this approach, then there is minimal impact on whatever the user is/was doing, but when they do navigate back to the EntityListViewController it has all the new entities already displayed.
Clearly, if the click on the custom URL could possibly delete an existing entity, then it would be more important to pop off any viewcontrollers related to that entity. But this is also something you could do using the same pattern -- have the model or controller post the notification, and then have the various UI elements listen for it and take appropriate action.