I am building an app using the Parse Library and trying to integrate Twitter auth. As Parse caches if a user is already logged in using
if ([PFUser currentUser])
I would like to show a different UIViewController depending if they are already logged in.
What is the best practice for this in iOS development, should I perform the checks in appDelegate or in some NavigationController.
How would I perform this in code?
The one of the solution is that you can add one view controller (root VC) as a home page where you check if the user is logged and base on the value you can call performSegue... method with different identifier depends on the value.
It will require add two view controllers in storyboard one for logged and one when the user is not logged and create two segue from the home VC to those views. You need to also named the segue differently, you will be passing the name to the performSegue... method.
I would lay out your storyboard with segue in the following way;
navController->loginController->mainController;
Assign an identifier to the mainController in Interface Builder. we'll call it mainVC for the purpose of this exercise. In viewDidLoad for the loginController, you can validate the user and transition to the mainController if the user is already authenticated.
E.G.
- (void)viewDidLoad{
[super viewDidLoad];
/// validate authentication status
if([PFUser currentUser]){
UIStoryboard *storyBoard = self.storyboard;
UIViewController *targetViewController = [storyBoard instantiateViewControllerWithIdentifier:#"mainVC"];
UINavigationController *navController = self.navigationController;
if (navController) {
[navController pushViewController:targetViewController animated:NO];
}
}
}
This will display the mainViewController without stopping for authentication.
Related
User opens the app and the LoginViewController is shown, once they enter their details and press the login button a NSURLRequest is made and if the result is success the app needs to load the HomeViewController.
Everything is working except for loading the HomeViewController.
I have tried instantiating HomeViewController and:
[self presentViewController:Home animated:YES completion:NULL]
But it doesn't load the new controller, I'm not sure if I need to segue or perform some other type of modal request I'm not aware of.
I assume Home is the class of the HomeViewController?
It would be more of cocoa style when its name was HomeViewController.
self.homeViewController = [[Home alloc]init];
By doing so you would have to care for creating and layouting all the views that the controller contols. If you do that with Interface Builder then use
self.homeViewController = [[Home alloc] initWithXib:#"NameOfTheNibFileHere"]];
If your view controller is layouted in a storyboard, then give it an ID "HomeID" in this example) within the storyboard and load it via.
self.homeViewController = [storyboard instantiateViewControllerWithIdentifier:#"HomeID"];
If it is the root view controller of your storyboard, then I suggest to use
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
and go from there.
You can do this with a segue, but you don't want to trigger the segue from your login button - you need to do it programmatically. In Image Builder, go to the bottom of your login view and control-click on the yellow View Controller icon in the black bar and drag to your HomeViewController. Select "Replace" as the segue type and then give the segue an identifier (say "HomeSegue") as per usual.
Now, once your login has completed you can trigger the segue using
[self performSegueWithIdentifier:#"HomeSegue" sender:self];
If the login fails then you can stay on the login view controller and display an error as appropriate
I have an app with a LoginViewController as the initial view.
Note: So in appDelegate.m, self.window.rootViewController is NOT the TabBarController.
After Auth, I present the main part of the app, which has a tabbarController (identifier:tabBar) with two tabs and one tab has a navigation controller. I am using Core Data, so I need to pass MOC.
If I use,
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UITabBarController *obj=[storyboard instantiateViewControllerWithIdentifier:#"tabBar"];
[obj setSelectedIndex:0];// Which tab to show first
[self presentModalViewController:obj animated:YES];
It works good visually. Now I need to pass the MOC. Read about PrepareToSegue method,created a segue (modal, Not shown in pic) from loginVC to my TargetViewController (TabBar>NavigationController1>View1), named the segue "LoginSegue" and used the following code:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UINavigationController *navController = (UINavigationController *)[segue destinationViewController];
View1 *devicelist = (View1 *)[[navController viewControllers] lastObject];
devicelist.managedObjectContext = managedObjectContext;
}
The TabBar does not show. How do i set the tabbar controller in this case?
I have been trying to get a grasp on getting a reference for the Modal Tabbar, but still not clear. Can some one explain in layman terms how to handle a situation like this?
I think it would be better to use a design that doesn't use a modal transition to the tab bar controller. Modal presentations are generally supposed to be for interruptions to the normal flow of the app, not for getting your main controller on the screen. There are two alternatives, that I think are better. You can leave the login controller as the initial root view controller of the window, but then switch it out for the tab bar controller (which will be the new root view controller of the window, and the login controller will be deallocated). This usually works ok, but I think in this case where you want to pass the MOC from the app delegate (I presume) to a controller in the tab bar controller, I think a second way would be better.
The second way to do this, and the way I usually do login controllers, is to have the tab bar controller be the root view controller of the window, and then present the login controller modally from the viewDidAppear method of the initial view (which would be the one you're calling View1). If you do this presentation with animation set to NO, the login controller will be the first thing the user sees:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
static int first = 1;
if (first) {
LoginViewController *login = [self.storyboard instantiateViewControllerWithIdentifier:#"Login"];
[self presentViewController:login animated:NO completion:nil];
first = 0;
}
}
The if statement is in there so the presentation doesn't happen again when you come back from the login controller (you could do something more sophisticated like having a delegate call back to View1 from the login controller indicating that the login was successful if you want, but this works).
If the login succeeds, you just dismiss the login controller, and you'll be there in your first view (if it fails, you just never dismiss it, and maybe put up a message saying the login failed).
If you go this route, then you can pass the MOC in the app delegate like this:
UINavigationController *nav = [(UITabBarController *)self.window.rootViewController viewControllers][0];
View1 *devicelist = (View1 *)nav.viewControllers.lastObject;
devicelist.managedObjectContext = managedObjectContext;
In my login logic I have a "AuthenticationViewController" (here the application checks if the user is already logged in). I also use storyboard and all views are based on push segues. And so it looks:
------ AccountViewController
|
--> AuthenticationViewController +
|
------ LoginViewController
Now when I use the UITabbarController and press twice on the tabbar icon "account", the application pops back to the AuthenticationViewController and then to the accountView or to the loginView. I know it's a "blemish" but how can I achieve that when I press twice at the account-icon, the AuthenticationViewController does not get shown? Or do I have a wrong logic?
EDIT
This is how my push-function gets initialized programmatically in the authenticationViewController:
// Delegate to AccountViewController if Data (Username, Password) is correct
if ([strResult isEqualToString:#"1"]) {
AccountViewController *AVC = [self.storyboard instantiateViewControllerWithIdentifier:#"AccountView"];
[self.navigationController pushViewController:AVC animated:NO];
// [self performSegueWithIdentifier:#"authAccountSegue" sender:self];
}
else {
LoginViewController *LVC = [self.storyboard instantiateViewControllerWithIdentifier:#"LoginView"];
[self.navigationController pushViewController:LVC animated:NO];
}
Well there's 2 things I see here, not sure if they'll help towards fixing your problem exactly though. The first is that you could just set up the segues for both actions and there wouldn't be much difference. The other is that looking at your storyboard structure the Login and Account controllers seem to be more suited to be a modal view instead of pushing it onto the stack.
Ok, solved this problem on my own: SUBVIEWS are the key :)
I am working on an app that at launch checks for valid login credentials, and if they are found and not expired the main split view controller is displayed, and if not a login screen should be displayed.
Each part is working fine separately, but I am struggling with the best way at launch time to select the proper view to display.
I have tried setting up a modal segue from the root view controller, and in my application:didFinishLaunchingWithOptions: function in the App Delegate, calling this:
// Segue to the login view controller...
if (loginNeeded) {
[self.window.rootViewController performSegueWithIdentifier:#"LoginScreen" sender:self];
}
This logically should work, but triggering segues from within the app delegate seems to be impossible.
What is the ideal place and technique for handling this?
You could try a custom segue, as per this post hiding-a-segue-on-login-process.
Alternatively if you're desperate to have the login display before the split view controller loads try something along the following lines...
Create your login screen on the main storyboard as, say, a subclass of UIViewController. Make sure it is the initial scene (check Is Initial View Controller).
On the storyboard, create a new segue from your login class to the original SplitViewController. Give it an identifier, 'Load SplitViewController' and a segue custom class name which we'll call FullyReplaceSegue.
In your login class .m file, add code to be called once the user has logged in:
[self performSegueWithIdentifier:#"Load SplitViewController" sender:self];
Create the new segue class, based on UIStoryboardSegue and name it FullyReplaceSegue as per above.
.h file
#import <UIKit/UIKit.h>
#interface : UIStoryboardSegue
#end
.m file
#import "FullyReplaceSegue.h"
#implementation FullyReplaceSegue
- (void)perform
{
UIViewController *dest = (UIViewController *) super.destinationViewController;
UIWindow *window = [UIApplication sharedApplication].keyWindow;
window.rootViewController = dest;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UISplitViewController *splitViewController = (UISplitViewController *)dest; // assumes we're transitioning to a UISplitViewController!
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
}
}
#end
Here's how I did it.
In didFinishLaunchingWithOptions:
//save the root view controller
[[self window] makeKeyAndVisible];
UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
rootController = [[navigationController viewControllers] objectAtIndex:0];
Somewhere else in the app delegate:
[rootController performSegueWithIdentifier:#"fileSegueID" sender:self];
Then, in the storyboard, create a segue from the view that gets assigned as "rootController", to the desired optional view, and give that new segue the id fileSegueID. It takes some debugging to make sure the rootController variable gets assigned to the correct view.
Maybe a little late, but I was looking for the same suggestions. Here's what I wound up doing.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Signup" bundle:nil];
if(isLoggedIn) {
UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
IndexController *ivc = [storyboard instantiateViewControllerWithIdentifier:#"IndexController"];
[navigationController pushViewController:ivc animated:NO];
}
Why don't you load the screen that would be visible assuming proper and non-expired log-in credentials (by setting it as the root view controller of the window), and then in viewDidLoad of that first view controller, check if an update to the login credentials are needed. If so, segue into the login view controller.
Yes, it can be used, if you get a reference to the segue's parent view controller. You can get it like this:
UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
[[[navigationController viewControllers] objectAtIndex:0] performSegueWithIdentifier:#"LoginScreen" sender:self];
This will only work if the index in viewControllers array matches the one of your view controller and if it exists of course. In this case is the first one (in the array and storyboard).
The segue ("LoginScreen") must not be attached to an action. The way you do this is by control-dragging from the file owner icon at the bottom of the storyboard scene to the destination scene. A popup will appear that will ask for an option in “Manual Segue”; pick “Push” as the type. Tap on the little square and make sure you’re in the Attributes Inspector. Give it an identifier which you will use to refer to it in code.
I have an application that can be used only if the user is authenticated. In particular, I created two different UIViewController. The first is called LoginViewController while the second is called HomeViewController. In applicationDidFinishLaunching: method, LoginViewController is created and then added to rootViewController property like this:
LoginViewController* loginCtr = ... // alloc and initiWithNibName...
self.window.rootViewController = loginCTr;
[loginCtr release];
Whitin LoginViewController I created a method that performs the login. When the user has been authenticated, I perform a method, called performLogin.
- (void)performLogin
{
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate switchView];
}
where swicthView method has been implemented inside the Application delegate class.
- (void)switchView
{
if(VIEW_TYPE == kLogin) // Display Login
{
// create a new LoginViewController and assign it to rootViewController
}
else // Display Home
{
// create a new HomeViewController and assign it to rootViewController
}
}
Given the previous code, is it possible to implement a more elegant mechanism to manage login / logout transition or does this type of implementation could be considered a valuable solution?
Thank you in advance.
Another option, I will set the HomeViewController as the rootViewController. In the viewDidLoad or viewDidAppear method (before I display any information on the HomeViewController), I will check whether the user has login credential. If not, I will display the loginViewController as a modal to request user login credential. In this case, I don't need to change the rootViewController of the window.
Starting from iOS 5 I started to manage login/logout transitions using the new containment API for UIViewControllers.
Implementing a Container View Controller
If iOS 5 is not available I would see a similar approach writing-high-quality-view-controller.
Hope that helps.