I receive a push which has an id of an object I need to look up. I save this to the app delegate.
In my main view controller, I fetch an index of all the objects.
I check if my app delegate has an id saved. If it does, I scan the objects I fetched from the index and push a detail view controller with that object.
The new view controller doesn't function properly and crashes when I tap back. Research indicated this is because my VC had a nil frame.
What's a good workflow for pushing a detail VC from a push?
With the information you have provided seems to be there is something wrong with your design.
Normally your user has a flow in navigation controller (1-2-3-4-5) and reaches to a point.
What you might be trying to do is (1-5) and when the user comes back it crashes because it haven't been able to create that state back. If this is the case there is something wrong with your design.
You can't push 5th element directly to a stack (a nav controller is a stack) without pushing first four. Reuse the code, trace the path normally user follows to create that state back.
Related
My app consists of two views. The first one is a GMSMapView and the second one is used to connect to a Bluetooth device that sends coordinates.
After the Bluetooth device is connected, I use a delegate to send the information back to the map view and move a marker around. To transition between views I was previously using segues, this didn't stop the Bluetooth view controller and the data made its way like I wished to the map view.
I ran into the problem of my map view being reinitiated so I decided to use a navigation controller. Now I use a push segue to get to my second view, and pop to come back to the same instance of the first one. Great, that worked! The issue I have now is that popping the second view seems to stop it completely from running in the background like it used to. Is there a way to keep it running in the background like it did before?
What I'm currently using to pop the second view is
self.navigationController?.popViewControllerAnimated(true)
Any idea would be appreciated! Thanks!
A popped view controller does not "stop running". It is returned to you, and if you don't retain it, it is completely destroyed.
If you don't want that to happen, retain it when it is returned. You are currently ignoring the returned view controller:
self.navigationController?.popViewControllerAnimated(true)
Instead, keep a reference to it:
self.mySecondViewController =
self.navigationController?.popViewControllerAnimated(true)
Be warned, however, that this is a very unusual architecture. You will not be able to use the storyboard segue to push again, because it will push a different copy. It would be better to abandon your navigation controller architecture entirely, as it is completely unsuited to the idea of a view controller persisting after it is popped. If you want an architecture where two view controllers persist simultaneously, you would be better off using a UITabBarController — or, even better, reorganize your app completely. The notion that you need the view controller to persist after being popped is a "bad smell": it means that you have put the functionality in the wrong place. Put the functionality in a place that does persist, rather than forcing the view controller to persist in some artificial way.
Summary of question
UINavigationControllerDelegate:didShowViewContoller makes it possible to get notified whenever any view controller has been displayed (as opposed to being loaded), provided its within the context of a navigation stack.
I want to know if such observation is possible for all view controllers if there isn't a navigation stack.
More background
I have an app where view controllers can suddenly appear based upon timers and local notifications firing, thus their appearance is effectively random.
If one VC triggers and gets displayed at the same time as another was in the process of getting displayed then there can be an issue (if you're experienced with iOS you'll be aware if one VC pushes another from within its viewDidLoad, rather than its viewDidAppear you will get an "attempting to present X on Y whose view is not in the window hierarchy" error)
How I solve this is I have a list of VCs to display and they get displayed by a view controller co-ordinator which implements UINavigationControllerDelegate's didShowViewContoller and doesn't display the new VC until didShowViewController has been invoked.
This works perfectly.
But now my problem is I want to do a similar thing for an app that isn't using a navigation controller, and thus I can't use UINavigationControllerDelegate:didShowViewController to observe globally when a view controller has been displayed. Does anybody know of another elegant mechanism for doing so?
Let's say I have a relatively complex storyboard with a login screen guarding access to a UITabBarController containing a couple of embedded UINavigationControllers and their children, like so:
The LoginViewController is the root VC, and decides if the user is logged in, either by checking stored credentials or asking for fresh ones. It then presents the UITabBarController as a modal.
There are four scenarios:
1) If the user starts the app from cold, they should see the ListViewController.
2) If the app is started from cold via a push notification regarding a "Foo", they should go directly to the FooDetailViewController.
3) If the app is warm-started via a push notification regarding a "Foo", they should go directly to the FooDetailViewController, no matter what VC they were looking at when the app was backgrounded.
4) If the user warm-starts the app, they should go back to whatever VC they were looking at when the app was backgrounded.
Where does the logic for all of this decision-making go? If I put it in the AppDelegate, I end up trying to push view controllers onto my hierarchy before they exist (Warning: Attempt to present ViewControllerX on ViewControllerY whose view is not in the window hierarchy!). The UITabBarController's viewWillAppear: lifecycle method does not seem to be being called on a warm start, and putting logic in every child view controller to check if it's been started from a push seems like coupling things that shouldn't be coupled (and in practice seems to lead to corrupted navigation stacks.)
How does everyone else structure this?
You must create that logic inside UIApplicationDelegate by creating properties needed to decide what is the view controller that has to be opened first.
The first place you have that guarantees the user interface (the window) is ready, is on the application delegate's -applicationDidBecomeActive:, but from here there are two courses of action:
For ListViewController and OtherListViewController is easy, you can just call -setSelectedViewController on UITabBarController;
For FooDetail and BarDetail, I think the best method is putting some logic on ListViewController, sending it a reference to the object you want to show the detail and by performing the relevant segue on ListViewController's -viewDidAppear:.
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.
I am new to iOS programming, i read the view controller programming guide but some things are still unclear to me
View of a viewcontroller is destroyed but viewcontroller remains, there nothing specified regarding if I my self want to destroy any viewcontroller or I just missed it. VC guide says iOS destroy VC when memory start getting short for other resources and same goes for VC's view right? Need to understand memory management of VC, please read my second point.
In a uinavigationcontroller i have a tableviewcontroller which display list of my friends, on selecting any of my friend i am pushing another VC of chat. The problem is every time on selecting a friend do i have to create a new instance of chat VC? how this chat VC will be unique for every friend i am trying to chat with? I am also saving messages in DB so who ever friend window open or close messages shows up, user don't get a blank view. Is it possible only one instant of chat VC is used for every friend uniquely(means only message of particular friend show and send to him to whom i am trying to chat). I think this problem is related to passing data between viewcontrollers but i am confused.
I hope i have clearly define what i am confused in. Please ignore any silly mistakes.
Thank you.
Answer to your queries -
View of a viewcontroller is destroyed but view controller remains
According to UIViewController guide -
It is responsible for creating those views and for relinquishing ownership of them at the appropriate times, including during low-memory conditions and when the view controller itself is released.
So we need to take the ownership to release a view controller.
Also The UIViewController class provides some automatic handling of low-memory conditions through its didReceiveMemoryWarning method, which releases unneeded memory.
In your Scenario-
TableView -> on click of cell push next view -> now pop this view to go back to table view(here memory got release) -> in table view again -> on click of cell a new view controller is pushed.
In this way it handles memory. If in a hierarchy we are pushing some views like -
VC1 -> VC2 > VC3 -> VC4
Now in this scenario navigation controller keeps instances of all these view controller, as navigation controller gives us flexibility of moving backward. And as soon as we go back that view controller is removed from stack and memory allocated to that is freed.
Syntax used -
Push a view controller -
[self.navigationController pushViewController:myViewController animated:YES];
[myViewController release];//at the time of push we add this release statement.
Now when we do a pop, its dealloc method is called and this is removed from the stack.
[self.navigationController popViewControllerAnimated:YES];//That view is release from navigation stack
Hope this clears your doubt.
Checkout this developer page for more details - http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html