Facebook Authentication iOS using Storyboard - iphone

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"];

Related

UINavigationController pushViewController stoppes working

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.

Where is UIWindow instantiated and the storyboard passed in?

I'm wondering if someone could explain (or point me in the right direction)
where the code for instantiating UIWindow disappears to when NOT using storyboards? In the empty-application project template the window is created in application didFinishLaunnching... in your AppDelegate.
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
However if using storyboards, the above code is omitted, though obviously UIApplication knows which window to start off with.
Where the application looks for the info.plist file to know which storyboard(s) to start off with.
I'm certain this is all well documented somewhere I just haven't found it. Read this Where is the UIWindow instantiated in an iPhone app? but not much help. I've been at iOS for awhile, just never had to mess with the initial startup of an app until now. Thanks
I think you meant 'where the code disappears to when you are using storyboards.'
The application loads the storyboard according to the "Main storyboard file base name" (UIMainStoryboardFile) key in your Info.plist, and from that storyboard it loads the view controller with the "Is initial view controller" toggle set.
Edit: As asked in the comments, the following code (similar to the initial loading in xib-based apps) will allow you to load and display a storyboard by name upon application launch:
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"StoryboardName" bundle:nil];
UIViewController *viewController = [storyboard instantiateInitialViewController];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
return YES;
}
While using storyboards, the storyboard to be loaded comes from your app's Info.plist file.
There will be a section in your Info.plist file with the key value pair like this:
<key>UIMainStoryboardFile</key>
<string>MainStoryboard</string>
In this case, MainStoryboard is the name of the default storyboard that is loaded.

selecting alternative first view controller from story board at application startup

I've just started on iOS programming and so far the tutorials and answers I found here have been a great help to move forward. However, this particular problem has been bumming me all night and I can't find an answer that "feels right".
I'm writing an application that connects to a remote service and the users need to sign in before they can use it. When they start using the application, their first view should be the sign in dialog; when they've authenticated before, they immediately see the overview page.
The project uses story boards - which I think is a great feature - so most of the code that selects and loads the root view controller is already taken care of. I thought the best place to add my logic is the application:didFinishLaunchingWithOptions: method of the AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
// select my root view controller here based on credentials present or not
return YES;
}
But this brought up two questions:
Inside this particular delegate method, the root view controller has already been selected (and loaded?) based on the story board. Could I move to an earlier spot in the loading process to override the first view controller selection or would that needlessly complicate matters?
To override the first view controller I need a reference to the story board, but I couldn't find a better way than to use the storyboardWithName:bundle: constructor of UIStoryboard. That feels wrong, the application should already have a reference to the story board, but how can I access it?
Update
I worked out the second issue I was having, as I found my answer here:
UIStoryboard: What's the Correct Way to Get the Active Storyboard?
NSBundle *bundle = [NSBundle mainBundle];
NSString *sbFile = [bundle objectForInfoDictionaryKey:#"UIMainStoryboardFile"];
UIStoryboard *sb = [UIStoryboard storyboardWithName:sbFile bundle:bundle];
The above will create a new story board instance; to get the active instance, it's a whole lot simpler:
UIStoryboard *sb = [[self.window rootViewController] storyboard];
In the story board file itself you have to set an identifier for the view you wish to load, e.g. LoginDialog. Afterwards you instantiate the view like this:
LoginViewController *login = [sb instantiateViewControllerWithIdentifier:#"LoginDialog"];
[self.window setRootViewController:login];
Within another view controller, the following suffices:
UIStoryboard *sb = self.storyboard;
LoginViewController *login = [sb instantiateViewControllerWithIdentifier:#"LoginDialog"];
[self presentViewController:login animated:NO completion:nil];
You can just reset the root view controller of the window
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
if(your_condition) {
UIViewController *newRoot = [your implementation];
self.window.rootViewController = newRoot;
}
return YES;
}
This is worked for me, Xcode5.0.1
I have a similar scenario as yours. My application uses a UINavigationController as the root view controller. If the user is logged in, I want to present him/her with NotLoggedInViewController, while if it's logged in I want to show the LoggedInViewController.
In a storyboard a UINavigationController can only have one child, so you have to be able to programmatically assign another root view controller to it.
I start by creating a custom navigation controller class, let's name it MyNavigationController. In the storyboard I assign this custom class to the navigation controller object.
Still in the storyboard, I then model both view controllers, and connect one of them to the navigation controller object. Since I need to be able to access them from my code later on, I assign each of them an identifier using the XCode inspector on the right. These identifiers which can be arbitrary strings, but to things simple I just use the class names.
Finally I then implement the viewDidLoad method on MyNavigationController class:
BOOL isLoggedIn = ...;
- (void)viewDidLoad {
id rootController;
if (isLoggedIn) {
rootController = [self.storyboard instantiateViewControllerWithIdentifier:#"LoggedInViewController"];
} else {
rootController = [self.storyboard instantiateViewControllerWithIdentifier:#"NotLoggedInViewController"];
}
self.viewControllers = [NSArray arrayWithObjects:rootController, nil];
}
I had hardly used storyboard & probably this is not the exact answer to your question. But I will suggest you some way what I did in my project created without using a storyboard.
In didFinishLaunchingWithOptions AuthenticationViewController is the first view loaded. It asks login credentials. Once entered it will enter the actual ViewControllers(viz. TabBar &all..) used by project.
Interesting feature added to project is, when you enter credentials I popped up an UIAleretView that asks user to choose one of the three options.
Save Credentials without passcode
Save Credentials with passcode
Dont save credentials
Here pass code is nothing but 4digit number entered by user. Whenever he wants to 'Save Credentials with passcode', I pushViewController that shows NumberPad instad of default keyboard & popviewController when it finishes entering of pin. If user 'Dont save credentials' & later on while playing the app wants to go for other authentication options then I added the last tab of TabBarController as 'Settings' tab inside which I allow user to choose one of the Authentication options, popped as UIAlertView in the beginning of app start after login.
Dont forget to Save credentials in keychain
In a nutshell,
AuthenticationViewController-> check if login credentials are stored in keychain
1.1. If not stored(i.e. 3. Dont save credentials)-> then show Login page.
1.2. If credentials are saved in keychain-> extract them & see if it is tied with passcode.
1.2.1. If it is tied with passcode(i.e. 2. Save Credentials with passcode
)-> then show passcode page.
1.2.2. If it is not tied (1. Save Credentials without passcode)-> then show/load you project's TabBarController hierarchy or other stuff. here actually your app start.
With the main storyboard already loaded, it's just a matter of finding its reference so that I can instantiate another root view controller:
UIStoryboard *mainStoryboard = self.window.rootViewController.storyboard;
self.window.rootViewController = [mainStoryboard
instantiateViewControllerWithIdentifier:#"view-controller-id"];

Using a root view controller to determine which view to load

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?

How do I do a View Transition in an iPhone app without allowing back?

This is a total noob question.
I have a starting view -- it's very simple: just some text and a button. When the user clicks the button, I want to go to the real "meat" of the application, which is a Navigation/Table View. How do I connect the button on the IntroViewController to a transition to the RootViewController? I don't want to make the IntroViewController a full Navigation controller and push the new view because that lets the user go back. I'm looking for some combination of code snippets and Interface Builder instructions.
If you're using a UINavigationController you could just use setViewControllers:animated:
You could even fake your "back" history if you wanted to go to someplace you've never been before.
For the comments below, here's what I have in my application delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
navController = [[UINavigationController alloc] initWithRootViewController: viewController];
[window addSubview:navController.view];
[window makeKeyAndVisible];
}