I have come across a situation today which has me wondering about best practices. I would greatly appreciate any comments on how you would approach this simplified example:
Let's say we have an app that requires a login to be useful. So, we have a couple of views and corresponding view controllers: LoginView and MainView. MainView is the root view and root controller for a navigation controller. LoginView is a view which allows the user to login.
So, the first time the app is launched, LoginView should be displayed, then MainView once the login is completed. On subsequent launches, only MainView will be displayed.
One approach to this would be to handle all of this in applicationDidFinishLaunching:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *rootController = [[MainView alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootController];
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
if ( notLoggedIn ) {
LoginView *vc = [[LoginView alloc] initWithNibName:#"LoginView" bundle:nil];
[rootController presentModalViewController:vc animated:NO];
}
return YES;
}
It would be nice to be able to handle this in a separate, dedicated "root" view controller. This controller would be loaded by the AppDelegate, and it would in turn load whichever view controller is appropriate. Can anyone offer advice on if this would be a better approach? And if so, how to go about it?
Is there a different approach you would recommend in a situation like this?
Thanks, all.
One approach would be to have a authentication provider which is a delegate. In your model classes which your views consumes, you can set the authentication provider. The delegate is a protocol which has a signature to authenticate and determine if authenticated.
The gui would provide the model with an authentication provider delegate which can answer whether authenticated and if not would present a modal view controller to authenticate. Different model methods would ensure authenticated (by asking the provider) and if not, would call authenticate on the providers delegate. Since the gui sets the auth provider, the model isn't breaking encapsulation and baking in gui interactions. the model is simply calling a callback.
That means it doesn't matter which view you're on and what state you're in. As any particular view crosses the model, if you're not authenticated you will get prompted. As another example, let's say the auth token times out after a period of time. How do you do that if auth is baked into one specific view on startup?
Related
Is there a good way to implement Facebook Authentication using Storyboard rather than xib files? It seems that the tutorial on the facebook developer site simply uses xib files.
Right now my code crashes at initWithNibName calls because my current project only uses Storyboard.
Thanks!
Code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.mainViewController = [[MyViewController alloc]
initWithNibName:#"MyViewController" bundle:nil];
self.navController = [[UINavigationController alloc]
initWithRootViewController:self.mainViewController];
self.window.rootViewController = self.navController;
[self.window makeKeyAndVisible];
I'm getting this code from this site:
https://developers.facebook.com/docs/ios/ios-sdk-tutorial/authenticate/
That tutorial is useless if you want to use storyboard because 90% of the tutorial is about setting up your user interface (not Facebook specific stuff). If you want to perform this tutorial with storyboards then setup your own UI first, then understand what they are doing when the Login button is pressed (and Logout).
The only things you need to do in your app delegate are:
Add the application:openURL:sourceApplication:annotation: method
Modify applicationDidBecomeActive: as they suggest
Check the state of the session when your app starts to determine if you show your login view or go straight to the main view (although you could also do this on the load of your first view).
You can put the code to open your facebook session (login) and respond to changes in the session state in the app delegate or elsewhere. Personally, I prefer to handle all the session management in a separate FB management class so as not to muddle up the app delegate with Facebook related code.
Your best bet is to just do this tutorial without storyboards to understand what they are doing and then adopt that into your own app, and/or read this page instead to understand the FBSession object.
I realized I could access my view controllers via the storyboard id's.
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewConroller* myViewController = [sb instantiateViewControllerWithIdentifier:#"MyStoryboardID"];
I have a problem with the UINavigationController. It about a client or serverside logout.
The idea for serverside logout is this, every 15 seconds a function is called that checks if the App is still logged in. If that is not the case then jump to the LoginViewController.
The Logout can also happen from the App itself. It executs simular code.
There are three relevant Controllers, LoginViewController is where we want to end up, SignOutController is where the 'Sign out'-Button is located and MainViewController.
Here are the relevant code parts.
First, the UINavigationController gets allcated like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
splashScreenViewController_ = [[SplashScreenViewController alloc] initWithNibName:#"SplashScreenViewController" bundle:nil];
uiNav_ = [[UINavigationController alloc] initWithRootViewController:splashScreenViewController_];
uiNav_.navigationBar.barStyle = UIBarStyleBlackTranslucent;
uiNav_.navigationBarHidden = YES;
[window_ setRootViewController:uiNav_];
[window_ makeKeyAndVisible];
return YES;
}
When the 'Sign out'-Button is pressed or the App figures out that the Server has forced a logout, this code is executed (same code, diffrent functions):
LoginViewController *loginView = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:nil];
[self.navigationController pushViewController:loginView animated:YES];
[loginView release];
If its a Serverside Logout it just reloads the MainViewController, every 15 seconds you see the animation of MainViewController sliding in. Its goes in a cycle from there, every 15 seconds it reloads.
If you click the 'Sign out'-Button it jumps to MainViewController instead of LoginViewController and starts the same cycle discribed above.
P.S. I have checked if any importend variable is nil, and I have checked that initalisation code is actually executed.
Edit: I think I did not mention that. My app works 99% of the time. Just one in a while this happen that the Sign-out button does not work and I start this cycle. Normally it works fine.
For the 15 second cycle where a new LoginViewController slides in it just seems you are not stopping to check if the app is logged in after realizing it wasn't. You should have some sort of boolean to store that and cancel the timer or whatever you use.
I don't understand what you say happens when you press the logout button, but I don't think you are making a good user interface.
I suggest you start the application by adding to the navigation controller the loginViewController as root. Then you add without animation the one you want to start with (for example MainViewController). Whenever the application is logged out of the service you pop the view controllers until the first one, which sould be the login one.
You have the method popToRootViewControllerAnimated: for that.
If you want to preserve the splash screen you can set it as the root view controller of the app, and chenge it to the uiNavigationController when you have finished loading.
The timer won't stop automatically just because you've pushed a view on top of another. It will be there until the controller it started on is released, which will only happen after it has been removed from the stack.
Also, you don't need to push MainViewController onto the stack after every check, you'll end up with multiple instances of it, each on top of another.
Also, without really knowing much about the architecture of the app, it would seem like a good idea to make LoginViewController modal, they really can't do anything if they've not logged in right? A modally presented viewcontroller wouldn't be affected by the navigation stack, and would also retain the users navigation stack much easier than having to manually push/pop controllers.
What is the right way to change XIB View which loaded at app start depending on some app settings. Of course I know how to get all settings I need.
In your application's delegate, in the method
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(yourSettings)
myViewController = [[MyViewController alloc] initWithNibName:#"FirstNibName" bundle:nil];
else
myViewController = [[MyViewController alloc] initWithNibName:#"SecondNibName" bundle:nil];
self.window.rootViewController = self.myViewController;
[self.window makeKeyAndVisible];
return YES;
}
And that's it. Probably you may want to save your settings in your user defaults so you can load the view properly.
However, I would use 2 different view controllers, as you probably want them to do different things, not just modify some graphics.
EDIT-
Let me see if I understand. You will always load the first view controller and, if some conditions are met, you modally want to present the second view controller that will get dismissed at some point, returning the user to the first view controller. If this is the case, I suggest you move the code in your first view controller, in viewDidLoad or better yet in viewDidAppear, as this view controller will always get loaded. Also this way the user can see that he will eventually go to that view controller. I use something like this in applications the user needs to login to so that it will be obvious for him that he cannot continue until he does login.
I can't say that this is the right way to do it, because it's up to the programmer how he arranges his code, but it would seem to me that the place that controls what view and how it appears belongs in a view controller and not in the delegate, especially considering that your first view controller always gets loaded. It should be up to that view controller to see if it presents the second one or not.
I have a rootviewcontroller and a loginviewcontroller. As soon as my app has finished loading up, I would like the rootviewcontroller to load the loginview associated with the loginviewcontroller. This is because the very first screen will be the user login.....
How is this possible?
The application flow should be as follows:
rootViewController -----immediately loads-----> loginviewcontroller -------user logs in--------> taken to mainmenuviewcontroller
The rootviewcontroller contains all the other controllers because it essentially holds global variables that store the results of processes carried out by other objects...
It depends on your overall UI architecture.
Without having much information, given the fact that it is a login view, I would push it as a modal view. Look at this document for more info.
In short, you can call presentModalViewController:animated: on your rootviewcontroller and pass it the loginviewcontroller. The login view controller will then be shown on top of the root view controller.
[rootViewController presentModalViewController:loginViewController animated:YES];
You should provide a way for your login view controller then to be dismissed and return control to the root view controller.
If u want to use first screen as login view and second one is root view controller then u use code as
- (void)applicationDidFinishLaunching:(UIApplication *)application {
viewController= [[loginViewController alloc] init];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:viewController];
[window addSubview:nc.view];
[window makeKeyAndVisible];
}
and then in loginViewController.m file call rootviewcontroller.
I have created a new iPhone "View-based Application" in Xcode. I then added a new "UIViewController subclass" and checked the "with XIB for user interface. Now the issue I have is that after hooking up all the variables and message handlers, I cannot push the new controller onto the stack using the following code:
[self.navigationController pushViewController:self.cabinetController
animated:YES];
All the variables and views are hooked up correctly, so all that I can think of is that its the way I am doing it, by pushing it onto the "navigationController". Is there something I am missing here? (I am very new to iPhone and Apple programming in general, so its probably a very simple oversight).
I realise that not enough information has been supplied ... here is a link to the project. Please note that it is an educational exercise has some creatively names classes.
http://files.me.com/nippysaurus/4yqz8t
In your appDelegate create a UINavigationController instance variable and then use your existing viewController as the rootViewController of the navigation controller.
e.g. in pure code using a UITableViewController (you can use xibs as well which your template app probably does).
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create root view and navigation controller
UITableViewController *rootViewController = [[[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped] autorelease];
self.navigationController = [[[UINavigationController alloc] initWithRootViewController:rootViewController] autorelease];
// Not necessary if you're using xibs
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Add the nav controller's root view to the window
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
return YES;
}
You need to change your view controller to a navigation controller, with its root view controller set as the current view controller.
If you examine your self.navigationController, you will realize it is nil. Messaging nil doesn't hurt, so no error message here.
Add another layer with a UINavigationController, and add your RandomShitViewController (nice name btw.) as its root view controller.
The navigation controller handles the push / pop part, your old controller manages its view.