I am hoping for a quick tip or strategy on this problem and see how you have or would solve this.
At launch, I am checking for the user "status." This basically means:
1. Check if they have a session in progress aka.. they are logged in
2. Check if they have entered in a Credit Card yet
3. Check if they have clicked on a confirmation email
Based on whether these statuses are true or not, I want to segue at launch to the appropriate viewcontroller (ex: if no CC detected, segue to a credit card entry page ... etc).
Currently, my iOS8 setup is as follows:
1. Checking user "status" in didFinishLaunchingWithOptions: in the appdelegate
2. I have a launch screen xib that (as far as I know) cannot have a viewcontroller attached (therefore I cannot segue at that point which would be most ideal).
Does anyone know a way to tackle this problem?
Thanks!
I presume you are using a storyboard. That's part of the problem here, but it is not insuperable. By the time your code in didFinishLaunching... runs, you already have a window and that window has a root view controller. Thus you can get a reference to that root view controller and do any pushing or presenting of a different view controller on top of it. And since you are doing this before the interface is shown, your interface will appear with the desired view controller showing.
But be careful not to do anything time-consuming. I don't know what all this "checking" involves but if it takes any time, the watchdog will kill your app dead for taking too long to launch. It is better to launch into your root view controller and then do your checking - even if this means the user will see the root view controller for a while. Just design a root view controller that is okay to show.
Related
I would like to create a "deep link" into my storyboard while preserving the backstack (back button navigation).
Ex:
Given the storyboard below (entry point being the leftmost Navigation controller)
When my application is opened via a remote notification I would like to open the second tab in by tab controller AND be able to navigate back to the item list via the back button.
Please note that I am not asking about how to open the second tab, or how to create such a storyboard but specifically if there is a way to do this with storyboards or will I have to do it by code.
Thanks!
PS: I come from an Android background where one recreates the parent view controller manually or (better) inserts it into the backstack. As far as my research goes there is no such thing in ios. I'm hoping I'm wrong.
Your UINavigationController has a viewControllers property. You can create as many view controllers as you want in an NSArray and assign it to this property and that will be the back stack with the last VC in the array displayed.
The problem is that when a notification arrives, your app could be in any state at all. It could be running, with some other screen showing. It could be suspended, with some other screen showing. Or it might not be running at all, and will now have to be launched from scratch.
Thus, starting in the App Delegate routine that responds here, you will have to deal manually (in code) with the situation if you want to put your app into an appropriate state.
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:.
I have a presentedViewController on top of the root view controller when the app gets dismissed. (e.g. User navigates to another app or goes back to the home screen.)
I would like to nil it out when the user reactivates the app without it being visible to them. Calling -dismissViewControllerAnimated: is not an option because it only works if the view controller is visible, and I'd like to do it sooner and specifically only in application:openURL:sourceApplication:annotation: and otherwise let the user continue their workflow in the modal view.
Are there any tricks I can use?
According to your comment, the only actual issue is what happens when the app receives application:openURL:sourceApplication:annotation: and is thus brought to the front.
In that case, simply adjust your interface in application:openURL:sourceApplication:annotation: - that is part of its purpose, to allow you to do that. You will be given some time before the "snapshot" is torn away, so if the adjustment involved is to dismiss an existing modal view, dismiss it without animation and there you are.
The user will still see the modal view for a moment, but not really - what the user is seeing is not your interface but the "snapshot" that was created by the system when the app when into the background. The "snapshot" is removed to reveal the actual interface (with a nice crossfade effect in iOS 7).
If the "snapshot" itself is problematic there are ways of preventing it from being taken, but it doesn't sound to me like this is that kind of situation.
(By the way, this raises the question of how the presented view is to know that it must be dismissed. The initial main action is in your app delegate, which is probably some conceptual distance away from the presented view. This would be an appropriate situation in which to use an NSNotification to communicate across this distance.)
I've been struggling with this a long time now, so I finally gave up on trying to find the answer and decided to ask it right away.
On my app I have a user log in page. The app has a TabBarViewController that has some NavigationControllers in it's items.
My last effort was to put the login screen embedded on a NavigationBarController and make it the Initial View Controller, as in the picture below.
When the app is launched, if the user is logged in the LogInViewController 'segues' to the TabBarController and everything is fine. When the user logs out in the ProfileViewController, there's a segue in this ViewController 'segueing' to the initial view controller.
In the other hand, if no user is logged in, the LogInViewController presents a view so that the user can insert username and password. If credentials are correct the LogInViewController 'segues' to the TabBarController. The problem is that at this point, even if the app is still working good, i get the following warning:
Warning: Attempt to present TabBarViewController: 0xa19a670 on UINavigationController: 0xa526370 while a presentation is in progress!
So I assume this is not the best way to handle all this LogIn/LogOut process.
My question is, where should I put the LogInScreen in the hierarchy?
If by any chance my layout/hierarchy is correct, how to make the warning go away?
You should make your Home screen as your rootViewController and in once your application starts or become active, you can check if user is logged in or not, if not then present the LoginScreen Modally, it will avoid the mess with other NavigationController or TabBarController
Alternate could be to put all the ViewControllers in a MutbaleArray and set the current Index of TabBarController according to the view you want to show? if you don't want to show the LoginScreen after user Logged in, just remove it from your MutableArray, check my answer here, it might help your cause
I have to support down to iOS 4.3.
My app outputs in the console :
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
As far as I know I'm not using two-staged rotation. I just have this method in my view controllers :
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
What else should I check in order to fix that?
Edit:
More precisions: my app uses a UITabbarController subclass. When the app starts, it checks if a user is logged in and then initiates the controllers of the tabbar controller if it's the case. If there's no user logged in, a modal view is presented (from the tabbar controller) the prompt the user to login and the controllers of the tabbar controller aren't initialized (yet). The "two-staged rotation" error is shown only at that moment and the rotation doesn't work.
So to summarize, the problem happens in that situation:
The rootViewController of the main window is the tabbar controller
The tabbar controller is empty (there are no view controller in the tabs and there's no tab)
A view controller is modally presented from the tabbar controller
OK I found a solution.
It seems like the presented modal view won't rotate until the viewControllers property of the UITabBarController is initialized. Since the concerned modal view is actually for login, I don't want to display anything behind it until the user is logged in because the views intended to be hold by the tabbar controller depend on the fact that a user is logged in.
So now just before presenting the modal view, I initialize the tabbar controller with a single, empty UIViewController and I remove it when the modal view is dismissed (i.e. a user logged in).
Perhaps it seems like a hack but it works well. And even if I don't understand why, it doesn't seem completely illogical that the tabbar controller doesn't behave like we want until it is fully initialized.
If someone has a better solution or explanation, feel free to comment :)
There aren't many posts regarding this error, so I'll admit my own shortcomings for the benefit of the next person so focused on the trees they might miss the forest. I found a missing
[super viewWillAppear:animated];
call inside my sub.