I'm working on an iPad app that has two controllers, a Login Controller and a View Controller. The Login Controller challenges the user for a username/password and once authenticated, there's a modal segue to the View Controller.
I've implemented a timeout wherein after 20 minutes of inactivity, the app segues back to the Login Controller. However, when the user logs back into the app, the state of the View Controller isn't preserved.
Is there a way to pass the View Controller object back to the Login Controller for re-use after logging into the app again? Is there a better way to manage the state?
Two possibilities come to mind...
You can create a model object either as a "singleton" or possibly owned by the application delegate and update it from the view controller and read from it whenever your view controller's view will appear.
The other option would be to have the view controller as the app's root controller and the login controller a modal overlay.
Your comment "Manage the state" is the answer you seek.
If there are changeable things about your view controller you'd like to save, then save them as they change, (either in NSUserDefaults, or CoreData, or some other persistent store) and have them populate when ViewController calls viewDidLoad.
Storing an entire UIViewController at the AppDelegate level just to preserve a handful of values is likely to be very wasteful, and won't help you at all if the app terminates. For this and many other reasons, your best bet is to follow MVC and make your model a persistent store which feeds the view.
Related
I have a little app that I'm building and wanted some opinions on the way I'm implementing login. Here's my plan:
1) Open app
2) Load ContainerViewController (The container view controller will play a little animation that shows the app is doing some loading)
3) The ContainerViewController checks if a token exists in the keychain. If a token does exist, then check if its valid or not.
3a) If the token is valid, add HomeViewController (a view controller that is the root of a navigation controller that is basically the main page of the app) as a child view controller.
3b) If the token is invalid, add LoginViewController (a view controller that basically handles the signing in process) as a child view controller.
If you sign in successfully at the login page, loginviewcontroller will be removed as a child and homeviewcontroller will be added as a child.
Is using container and child view controllers the right way to go about handling the opening of the app? By the way, I'm building it completely programmatically, which means I'm not using storyboards so segues can't be used (unless I'm totally wrong there). Thanks guys
This is the right approach to implementing the login process. In short Up on login, you check for the token, if it is there and valid you present the MainViewController. Else you present the LoginViewController.
PS(Thumbs up for developing it programatically! I like that way as well)
I am trying to figure out the best UX for handling 'protected' view controllers throughout an iOS app, that require authentication. For example, say certain view controllers require authentication in my app. If a user navigates to that view controller I can have a check in viewWillAppear that checks if a user is authenticated and show the login screen if they are not. But here is the tricky part... What happens if they cancel the login? A whole set of issues can arise. I can pop the view controller they are on, but what if the previous controller they came from also requires authentication. I can potentially end up with a chain of popping view controller and showing a login view... This seems like an awful user experience.
I can think of one solution which is working now but doesn't seem correct:
An unauthenticated user enters a 'protected' view controller
Replace the AppDelegate's window.rootViewController with the login screen
When the user logs in successfully they are brought to the first screen of the app (losing the state of where they were in the app).
I assume that this is a common problem that other devs have faced. I couldn't find any best practices for handling this and figured you guys would be able to recommend some tips?
Add a flag in Login view controller that you are coming to the protected view controller from Login View and set that flag to YES when user clicks cancel button on login screen and use popToRootViewController to pop to root view controller. and in rootViewController's viewWillAppear, set flag to NO.
User doesn't need the other protected view controllers if they select cancel in login.
and as per my observation, user doesn't have a chance to go to multiple protected view controllers at once, because, if user comes to first protected view controller, you are showing him login screen and if he cancels it, you are pop ing the view. So there is no chance for user though. either way, the above method works for you.
my situation is:
My app need to display the views normally but when I press and call a view that will display some sensitive information, I need to be logged, so a login view need be displayed. The trick for me is: when I call presentViewController and load the view, the view is called in a modal way that hide the tab bar and I can`t access other views.
Other thing is I`m doing the check on if user is logged in viewDidAppear, is that a bad practice?
tks for any reply.
In such cases, the concept of container view controller is your friend. You have a UIViewController that encapsulates all the business logic, in your case, the login logic. It manages a view that, say, has a form-like structure where the user enters the data. You can add this as a child of the parent view controller (where your tabs exist). After he presses login (or something), you can remove this child view controller and continue where you left off!
No, checking in viewDidAppear for session validation is something that I do all the time, I think you are fine here.
Edit
In response to a comment. Yes, session validation viewDidAppear can cause problems. A careful application design is the key here. I personally store the login information of the user (as a user model) in NSUserDefaults and remove it whenever the user logs out.
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 know this question is a bit open-ended but I'm trying to make it as specific as possible because I don't really know what a standard convention would be for this type of thing with iOS. I have been looking into implementing a login screen on my app, however I am having trouble figuring out the best way. I have three methods listed below, could anybody tell me if one of these is better or a more correct approach? (or if there is something that I totally missed).
The first approach - I initially have a navigation controller as my root controller with the login screen being the first view on the stack. Then when the user logs in, i just push the main UI to the stack. The user can then use the back button to return to the login screen. I know this technique works but then when re-launching the app, the user doesn't need to login again. I'm not sure should I just set up the stack the same way (login page first) and then push the main UI on top? Is that a common technique?
The next approach, I found here how to replace the root controller How to change RootViewController (in AppDelegate) from within CustomViewController? I like this idea better where I could just get rid of the login screen after login and add some type of button to bring it back on logout. Then during re-launch I can just skip adding the login screen altogether.
The third approach is perhaps overlaying the login on top of the main UI but I haven't found any code to do this. It also seems a little weird because my main UI might start firing messages that i wouldn't want to occur until after login so I'd have to account for that.
I would suggest you to have a root view controller with a blank view. This controller is set as a root view controller in the AppDelegate's applicationDidFinishLaunching: method.
Role of this root view controller would be to show the login screen through login view controller initially. Login view controller delegates to root view controller upon successful login to remove itself from root view and root view controller then set the next controller to be displayed.
This way you can manages multiple views if any before showing the main view through your root view controller.
There are different ways to display login view on top of root view.
1) Present a login view controller without animation.
2) Add login view as a subview on top of root view [Note: Before iOS 5, you don't get the rotation and view life cycle call backs and you should write your own code to pass the callbacks to the view added on top of root view. From iOS 5 onwards you have view containment concept using which you can maintain parent child relationship.]
Hope this helps.
We have a similar app. Our storyboard looks a little like a wheel.
We have a navigation controller as our root controller, which then goes to a view controller with a spinner on it. That controller handles the call to see if the user is logged in or not. It is connected to two different controllers.
If they are already logged in, it goes to the main view controller. If they haven't logged in, it goes to the login view controller. The login view controller will go back to initial view controller on success, and since they'll then be logged in, it goes to the main controller.
To fall back, we go all the way back to the initial view controller with the spinner on it.
You can hide the navigation bar on every view before the main one.
I always implement it with storyboard. Just add Storyboard ID to your login view controller, say Onboarding.
Then write something like this in application: didFinishLaunchingWithOptions:
if (![[NSUserDefaults standardUserDefaults] objectForKey:TRAIN_COMPLETE]) {
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.window.rootViewController = [storyboard instantiateViewControllerWithIdentifier:#"Onboarding"];
}
All our ViewController are subclassed from a custom subclass of uiviewcontroller. This allow us to make powerful enhancements on behavior, constraints, ...
Our super view controller class handle authentification by summoning a modal view on viewWillAppear if user is not logged in.
Works like a charm to handle disconnection and resuming from any point in the app.