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.
Related
We are building a data acquisition app that allows the user to enter other view controllers (preferences screen, log screen) brought forth by
[self presentViewController:secondary_view animated:YES completion: nil].
When certain data arrives, we want to shut down all of these secondary views summarily. We are using
[self dismissViewControllerAnimated:false completion: nil];
to do this. What we find, though, is that if the data comes in while the new view is sliding into place (not done animating), then the dismiss command has no effect, and the secondary view is still there, and in fact the app gets confused about its own state in a way that makes it appear to "hang."
Is there a better (more reliable) way to make sure that all presented view controllers are dismissed, even if they are currently animating?
My recommendation would be to present the scene using Interface Builder (storyboards) and set the Segue Identifier to a certain string with the type of presenting you're wanting done: show, show detail, modally or popover.
(see below)
Once you've done this, you can use the method: prepareForSegueWithIdentifier, where you can be assured the data exchange between the segue.destinationViewController, the View Controller you're sending the data too, and the current View Controller is performed.
(see below)
NOTE
You can perform a segue programmatically using performSegueWithIdentifier: sender:
Also, be sure to remove all the strong references to the data objects that are not being released in your current scenario. Try doing this within the Method above after you've transferred the data to the destination View Controller.
In one view controller the user is able to restore the coredata database. After this restore a lot of stuff probably doesn't work anymore in the other view controllers already in memory even though I've rebuilt the stack. So I just want to get rid all of them, so that always the viewDidLoad methods gets triggered again if reopening again one of these view controllers.
How can I force that?
I'd say you should put the load data code of each view controller in another function than viewDidLoad, for example reloadData. All of your view controllers then also need to be subscribed to a notification (e.g. reloadAllViewControllers). Then using [NSNotificationCenter defaultCenter] postNotificationName:#"reloadAllViewControllers" you can call all reloadData functions to reload all your viewControllers.
Very simple use case: Let's say an iOS app displays a MovieListController view (inside of a UINavigationController) with a list of movies. When the user touches on one, the app pushes a MovieDetailController onto the navigation stack (i.e. [[MovieDetailController alloc] initWithMovieId:(NSString *). In the MovieDetailController's viewDidAppear: method, it makes an HTTP call to retrieve details based on the movie ID passed into it.
The challenge is that the MovieDetailController gets pushed onto the navigation stack right away, and for a second or two while the details haven't been retrieved, the view shows a bunch of blank fields, which is undesirable.
To get around this, I'm thinking of having the MovieListController not push the MovieDetailController onto the stack right away. Instead, it would put up a progress indicator (I'm using SVProgressHUD), then call MovieDetailController's initWithMovieId: method which would kick off the HTTP call. Then when the data is received, the MovieDetailController would make a callback back to MovieListController to remove the progress indicator and then push the MovieDetailController onto the navigation stack.
Is there a better pattern for this type of scenario? Should I be considering having the MovieDetailController push itself onto the navigation stack when it's ready?
Note: I have considered loading the detail view and putting up an activity indicator, but you'll still be able to see an 'empty view' behind it which looks a bit weird. I have also considered just having the MovieListController retrieve the details itself but this seems to break the encapsulation model - the MovieListController should just be concerned about listing movies, not about their details.
Any thoughts? This Movie stuff is just an example - looking for a general pattern here.
Personally I would take the following approach.
User selects the movie they want details for
Push to the detail view and rather than showing your skeleton view with empty fields, overlay a loading view, you could continue using your progress HUD on top of this for any animations you gain with that.
Once the results come down, remove your HUD and the loading overlay view that is hiding all the data/fields
The reason I would go this route rather than showing the HUD before pushing the view controller is that you can give the user the opportunity to cancel their selection. I am not familiar with SVProgressHUD but hopefully when a HUD is displayed you can enable touches, specifically the user touching Back on your UINavigationController in the event they accidentally selected the movie or the request is just taking longer than they are willing to wait for.
It also separates the logic form your list view, your detail view is standalone and could be initialized anywhere in your app (maybe you want to cross link similar movies within a movie detail view) and you do not need to rewrite the logic of the presenting view waiting on the results to come back.
In this situation, I personally would return to the model-view-controller pattern.
There are several system apps that display a detail view from a list of objects, e.g. Calendar, Contacts, etc. Presumably, as with EKEvent and ABPerson in their respective apps, the main view controller maintains a list of the model objects. When the user selects one of the items, the main view controller passes the selected model object to the detail view controller. The detail view controller itself doesn't have to do any data loading. So, like #ChrisWagner said, we want to separate the logic from the view controller.
Method
Similarly, you might want to use a MovieList class that stores an array of Movie objects. Each Movie stores the values for all the fields in the detail view controller - essentially, all the information the app needs about the movie. For example, you might have a property NSString *movieTitle, or NSDate *premiereDate. The movieTitle would be set by the MovieList at initialization because it's just metadata; on the other hand, the premiereDate might be nil if the data hasn't loaded, so you would have a property BOOL isLoaded to check for this condition.
You could then proceed in one of two ways:
1) Say the main view controller wants to push a detail view controller for a movie. Then the main view controller would dig the appropriate Movie out of the MovieList and check if it's loaded. If not, it would call something like -(void)loadContents on the Movie. When the model object is finished loading, it will post a notification that it's finished loading. At this point the main view controller will dismiss its progress view and push the detail view. If you use (1), it's not as important to use a MovieList coordinator.
2) If you want to be more aggressive about loading the movie information, you could implement a method on MovieList that calls loadContents on its Movies in the background. Then there's a higher chance that a movie will already be loaded when the user selects it.
Edit: Note that if you decide to use a MovieList type object, only the main view controller should be allowed to access this list. In a way, the model structure I've described parallels the view controller structure. The detail view controller doesn't know about the list view controller, so it shouldn't know about the MovieList either.
Benefit
The benefit of using this Movie and MovieList structure is that the data model is completely separated from the view controllers. That way, the user is free to cancel, select, present and dismiss view controllers while the data is loading. (Imagine if the user pressed Back to dismiss the progress HUD, canceling any HTTP data it would have gathered. Then, if he decides to come back to that same movie, the new detail view controller has to start loading afresh.) Also, if you decide to replace view controller classes in the development process, you won't have to copy and paste a bunch of data-handling code.
I think you'll find that structuring your app in this way not only gives you and the user more freedom, it also makes your app efficient and open to later extensions. (Sorry to post so late, but I wanted to include MVC in this pattern-related discussion!)
I think a more advanced route would be to start the detail requests as the cells are being being shown on in the tableview/collection view. Then as the cells move offscreen, cancel the requests. You might be loading movie details unnecessarily, but I don't think this is a big deal, because you'd only be filling your db with movie details you might not need and coredata can handle that. This is assuming that your api requests are happening on a bg thread and they don't affect the main(UI) thread.
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 wondering if there's any way -viewWillAppear: would be called without a matching -viewDidAppear:. Ditto for -viewWillDisappear and -viewDidDisappear.
The root of my question is where to register and unregister for KVO and / or NSNotifications of an object who's change notifications will cause the view controller to update views.
For example, I have a model object that is being processed asynchronously and it's string properties could change. I'd like the view controller to KVO those properties and have any changes reflected by swapping out the text of a label managed by said view controller.
Where do you register and unregister for notifications and why?
EDIT:
A gotcha I've come across is what to do in cases of application state change (e.g. -applicationWillResignActive, -...didEnterBackground, etc). These changes don't seem to trigger view controller lifecycle methods. Any best practices here?
With the standard container view controllers, you will always get will/did messages in pairs. If you have written your own view controller container, you may not, but that would be a bug in the container implementation.
Most of the time, you'll want to set things up and tear things down in the 'will' messages. This gives your view controller the earliest shot at anything it needs to do before it becomes "active" and also shuts down things as early as possible when you no longer need them.
When pushing a view controller in nav stack, it is entirely possible that a notification will occur during the push animation. If you set up the observers in viewDidAppear, you would miss that notification. You want to be listening as soon as possible.
Likewise, I would reckon that viewDidDisappear is too late to remove callbacks. For example, a location manager could be stopped in viewDidDisappear, but another location update could be delivered during the disappearing animation. That probably doesn't hurt much, but depending on the application, something weird could happen like an alert view appearing after you've already left a view controller - which looks like a flicker to the user.
Anything non-view related, as indicated above, occur in the 'will' methods. The choice about will vs did, then, is really about what the user sees. Animations should start in viewDidAppear, otherwise, the user won't see the frames that occur during will/did appear. Data should be moved to views in viewWillAppear, otherwise, a blank view will transition in and the data will only appear after the transition animation completes. Also, it possible that a view frames could be adjusted in between viewWillAppear/viewDidAppear, like in the case of a previous view controller in the stack hiding/showing the navigation bar.
And on a side note, not something I will get into great detail here with, but I'd advocate against KVO for controller interactions that move data from model to view objects. Difficult to test and difficult to trace.
You can subclass your UILabel and in your subclass override the setText method:
-(void)setText:(NSString *)newText {
//do your KVO updates here
[super setText:newText];
}
hope this helps