I have a small app that attempts to do basic login with FB using the exact methodology used in the Facebook tutorial for logging in here: https://developers.facebook.com/docs/howtos/login-with-facebook-using-ios-sdk/
The desired behavior is that the user be able to login to FB and stay logged in until they have explicitly chosen to log out by clicking on the logout button. They should stay logged in across launches of the app, even if the app is forcibly killed (but not deleted).
What is happening is that after clicking on the Login button, the app correctly logs the user in, but once I navigate back to the root view controller, and then back to the FacebookViewController, the button shows Login again, instead of log out. Something somewhere is killing the session.
I am not sure what code will be asked for, but here is a bunch:
#import "FacebookViewController.h"
#interface FacebookViewController ()
#end
#implementation FacebookViewController
#synthesize authButton;
#synthesize postSwitch;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(sessionStateChanged:)
name:FBSessionStateChangedNotification
object:nil];
// Check the session for a cached token to show the proper authenticated
// UI. However, since this is not user intitiated, do not show the login UX.
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate openSessionWithAllowLoginUI:NO];
// Set toggle
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"] isEqualToString:#"Yes"]) {
//set to yes
[postSwitch setOn:YES animated:YES];
} else {
// set to no
[postSwitch setOn:NO animated:YES];
}
NSLog(#"postToFB is now %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"]);
}
- (IBAction)authButtonAction:(id)sender
{
AppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
// If the user is authenticated, log out when the button is clicked.
// If the user is not authenticated, log in when the button is clicked.
if (FBSession.activeSession.isOpen) {
[appDelegate closeSession];
} else {
// The user has initiated a login, so call the openSession method
// and show the login UX if necessary.
[appDelegate openSessionWithAllowLoginUI:YES];
}
}
- (IBAction)togglePostSwitch:(id)sender
{
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"] isEqualToString:#"Yes"]) {
//set to No
[[NSUserDefaults standardUserDefaults] setObject:#"No" forKey:#"postToFB"];
} else {
// set to yes
[[NSUserDefaults standardUserDefaults] setObject:#"Yes" forKey:#"postToFB"];
}
NSLog(#"postToFB is now %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"]);
}
- (void)sessionStateChanged:(NSNotification*)notification
{
if (FBSession.activeSession.isOpen) {
[self.authButton setTitle:#"Logout of Facebook" forState:UIControlStateNormal];
} else {
[self.authButton setTitle:#"Login to Facebook" forState:UIControlStateNormal];
}
}
#end
The MainViewController (the root VC) has the following in the ViewDidLoad, and the ViewWillAppear:
- (void)viewDidLoad
{
[[self navigationController] setNavigationBarHidden:YES animated:NO];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
BOOL open = [appDelegate openSessionWithAllowLoginUI:NO];
if (open) {
NSLog(#"User session found (MainVC viewDidLoad)");
} else {
NSLog(#"no session detected");
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:NO];
[[self navigationController] setNavigationBarHidden:YES animated:NO];
}
I am not real clear on why this behavior is occurring. Any help would be greatly accepted :)
I actually traced this back to an error in labeling the login button. The session was always open even when the button said 'log in'. I managed to correct this issue, but now I am discovering that indeed the app will not stay logged in across launches, but at least the login/logout button is always correct!
Related
I know similar questions are asked but none of them helped.
My Observation
There are two cases: 1) When app is running and in the foreground, visible to the user 2) User presses home button and app moves to the background, not visible to the user. In case 1) when a phone call comes and end I get all the events with no problem. In case 2) I get no events, but when user opens the app, then I get all the events that has happened when app was in the background.
My Question
How can I get my code working even when app is in the background? Or, how can I move my app from background to foreground when those events happen?
What I've tried
I've tried enabling background fetch at Background Modes at Info.plist with an implementation of -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler. It had no effect.
Below is my code:
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// some other code above
self.callCenter = [[CTCallCenter alloc] init];
[self.callCenter setCallEventHandler:^(CTCall *call)
{
UINavigationController *nc = (UINavigationController *) [[(AppDelegate *) [[UIApplication sharedApplication] delegate] window] rootViewController];
MainViewController *vc = nc.viewControllers[0];
[vc setCallState:call.callState];
}
}
MainViewController.m
-(void)setCallState:(NSString *)callStateString
{
if ([callStateString isEqualToString:CTCallStateConnected])
{
NSLog(#"call connected!");
} else if ([callStateString isEqualToString:CTCallStateDialing])
{
NSLog(#"call dialing!");
} else if ([callStateString isEqualToString:CTCallStateDisconnected])
{
NSLog(#"call disconnected!");
} else if ([callStateString isEqualToString:CTCallStateIncoming])
{
NSLog(#"call incoming!");
} else
{
NSLog(#"unknown call state %#", callStateString);
}
}
I'm new to iOS and trying to implement Facebook login and after login is successful I want it to go to another view. I've done everything that the documentation shows but I can't get the delegate to be called. I have seen people questions but I tried all of them. Nothing worked not sure if I miss something obvious.
This is my LoginViewController.h
#import <UIKit/UIKit.h>
#import <FacebookSDK/FacebookSDK.h>
#interface LoginViewController : UIViewController <FBLoginViewDelegate>
#end
This is my LoginViewController.m file
#import "LoginViewController.h"
#interface LoginViewController ()
#end
#implementation LoginViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"Starting");
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
FBLoginView *loginView = [[FBLoginView alloc] init];
loginView.delegate = self;
return YES;
}
-(void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
NSLog(#"You're logged in");
[self performSegueWithIdentifier:#"segueToAnotherView" sender:self];
}
-(void)loginViewShowingLoggedOutUser:(FBLoginView *)logoutView {
NSLog(#"Logged out");
}
#end
I also have this in my AppDelegate.m
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// Call FBAppCall's handleOpenURL:sourceApplication to handle Facebook app responses
BOOL wasHandled = [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
// You can add your app-specific url handling code here if needed
return wasHandled;
}
When the app comes back I don't see the log "You're logged in" or even "Logged out" at all. I can only see the Logout button from Facebook. Also, the two view controllers are connected by a segue.
You are going to want to add your app to Facebook, Download the Facebook SDK and add some values to your plist. There is a lot more you're going to need to do here and I suggest you read Facebook SDK Documentation If you need more help, I am brand new to iOS as well and just implemented my own Facebook login (I guess you could call it custom) and it works smoothly.
Checkout the following code to log in from facebook
#define PERMISSION #"publish_actions",#"email",#"public_profile"
#property (strong, nonatomic) FBSession *session;
NSArray *permissions = [[NSArray alloc] initWithObjects:PERMISSION,nil];
self.session = [[FBSession alloc] initWithPermissions:permissions];
[FBSession setActiveSession:self.session];
// if the session isn't open, let's open it now and present the login UX to the user
[self.session openWithCompletionHandler:^(FBSession *session,
FBSessionState status,
NSError *error) {
if (!error) {
//Handle code for success
} else {
//Code for failure
}
}];
In my HomeViewController's viewDidAppear method, I have the following code:
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL didRunBefore = [defaults boolForKey:#"didRunBefore"];
if (!didRunBefore) {
// check to see if children already exist (previous user)
NSArray *children = [CoreDataHelper getObjectsForEntity:NSStringFromClass([Child class]) withSortKey:#"name" andSortAscending:YES andContext:self.managedObjectContext];
if (children.count == 0) {
// send user to create fist child
UIStoryboard *storyboard = self.storyboard;
ChildEditTableViewController *editController = [storyboard instantiateViewControllerWithIdentifier:#"ChildEditControllerID"];
NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init];
newContext.parentContext = self.managedObjectContext;
editController.managedObjectContext = newContext;
[self.navigationController pushViewController:editController animated:NO];
}
}
}
Here's the code from ViewDidLoad in ChildEditTableViewController:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"Child Edit controller loaded");
self.availablePicker.delegate = self;
self.bankedPicker.delegate = self;
self.carryOverCellIsShowing = NO;
self.isNewChild = self.child == nil;
self.imageButton.layer.cornerRadius = self.imageButton.frame.size.width/2;
self.imageButton.layer.masksToBounds = YES;
[[self.imageButton imageView] setContentMode: UIViewContentModeScaleAspectFill];
if (self.isNewChild) {
// check to see if it's user's first time running app
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL didRunBefore = [defaults boolForKey:#"didRunBefore"];
if (!didRunBefore) {
// hide Home back button
[self.navigationItem setHidesBackButton:YES];
// update didRunBefore to yes
[defaults setBool:YES forKey:#"didRunBefore"];
[defaults synchronize];
}
self.child = [NSEntityDescription insertNewObjectForEntityForName:#"Child" inManagedObjectContext:self.managedObjectContext];
self.title = NSLocalizedString(#"Add New", #"Add New Title");
}
else {
if (self.child.profileImage != nil) {
[self.imageButton setImage:[UIImage squaredImageFromImage:[UIImage imageWithData:self.child.profileImage] scaledToSize:self.imageButton.frame.size.height] forState:UIControlStateNormal];
}
self.name.text = self.child.name;
self.autoBankSwitch.on = [self.child.autoBank boolValue];
self.carryOverSwitch.on = ![self.child.resetDailyTotal boolValue];
[self setCarryOverSwitchVisibility:self.autoBankSwitch];
}
// This will remove extra separators from tableview
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
}
That code works fine as far as I can see the ChildEditTableViewController load on the screen, but then it automatically pops back to the home controller. I've checked the code in the child controller and the only time I pop the controller is when the user hits a button.
Here's the Save IBAction where I pop the controller:
- (IBAction)save:(UIBarButtonItem *)sender {
[self saveToDB:sender];
[self.navigationController popViewControllerAnimated:YES];
}
If I use self.navigationController setViewControllers instead, this does not happen and ChildEditTableViewController stays loaded on the screen, but clicking the Save button (which pops the view controller) doesn't do anything.
Any ideas? (Thanks!)
**** EDIT *****
I noticed it was working fine in iOS 7.1 and 7.03. The only difference from an UI perspective was this piece of code below:
// enable handling of push notifications
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
// use registerUserNotificationSettings
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge
|UIRemoteNotificationTypeSound
|UIRemoteNotificationTypeAlert) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
} else {
// use registerForRemoteNotifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}
In iOS 8 I was getting a prompt to allow notifications on the simulator (something that doesn't work on the sim in prior versions). After I clicked ok is when the EditChild controller would get popped. So I commented out that code in the app delegate and the controller stays loaded just like in iOS 7.
****** EDIT ******
Below is the ApplicationDidBecomeActive code
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"%s", __PRETTY_FUNCTION__);
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
// move user to home screen so app is locked each time they open it (but not on first use)
SWRevealViewController* revealController = (SWRevealViewController*)self.window.rootViewController;
UINavigationController *nav = (UINavigationController *)revealController.frontViewController;
[nav popToRootViewControllerAnimated:YES];
}
So this is the culprit. This code is getting called again right after the user clicks Accept on the notifications registration alert, for some crazy reason.
I am thinking that the the callbacks in your application delegate are doing something to your view/controller hierarchy. I would add some break points in your application delegate methods applicationWillResignActive:, applicationDidBecomeActive: and see if they are doing anything.
I want my root view controller of the program that I am working on to be a PFQueryTableViewController, however, I need the app to make sure that a user is logged in before the PFQueryTableViewController attempts a query.
I tried creating a RootTabBarViewController whose initial view is the PFQueryTableViewController and putting the following in the RootTabBarViewController's .h and .m files:
//
// RootTabBarViewController.h
//
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface RootTabBarViewController : UITabBarController <PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate>
#end
//
// RootTabBarViewController.m
//
#import "RootTabBarViewController.h"
#import "PlannrLogInViewController.h"
#interface RootTabBarViewController ()
#end
#implementation RootTabBarViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (![PFUser currentUser]) { // No user logged in
// Create the log in view controller
PFLogInViewController *logInViewController = [[PFLogInViewController alloc] init];
[logInViewController setDelegate:self]; // Set ourselves as the delegate
// Create the sign up view controller
PFSignUpViewController *signUpViewController = [[PFSignUpViewController alloc] init];
[signUpViewController setDelegate:self]; // Set ourselves as the delegate
// Assign our sign up controller to be displayed from the login controller
[logInViewController setSignUpController:signUpViewController];
// Present the log in view controller
[self presentViewController:logInViewController animated:YES completion:NULL];
}
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#pragma mark - PFLoginViewController Delegate
// Sent to the delegate to determine whether the log in request should be submitted to the server.
- (BOOL)logInViewController:(PFLogInViewController *)logInController shouldBeginLogInWithUsername:(NSString *)username password:(NSString *)password {
// Check if both fields are completed
if (username && password && username.length != 0 && password.length != 0) {
return YES; // Begin login process
}
[[[UIAlertView alloc] initWithTitle:#"Missing Information"
message:#"Make sure you fill out all of the information!"
delegate:nil
cancelButtonTitle:#"ok"
otherButtonTitles:nil] show];
return NO; // Interrupt login process
}
// Sent to the delegate when a PFUser is logged in.
- (void)logInViewController:(PFLogInViewController *)logInController didLogInUser:(PFUser *)user {
[self dismissViewControllerAnimated:YES completion:NULL];
}
// Sent to the delegate when the log in attempt fails.
- (void)logInViewController:(PFLogInViewController *)logInController didFailToLogInWithError:(NSError *)error {
NSLog(#"Failed to log in...");
}
// Sent to the delegate when the log in screen is dismissed.
- (void)logInViewControllerDidCancelLogIn:(PFLogInViewController *)logInController {
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - PFSignUpViewController Delegate
// Sent to the delegate to determine whether the sign up request should be submitted to the server.
- (BOOL)signUpViewController:(PFSignUpViewController *)signUpController shouldBeginSignUp:(NSDictionary *)info {
BOOL informationComplete = YES;
// loop through all of the submitted data
for (id key in info) {
NSString *field = [info objectForKey:key];
if (!field || field.length == 0) { // check completion
informationComplete = NO;
break;
}
}
// Display an alert if a field wasn't completed
if (!informationComplete) {
[[[UIAlertView alloc] initWithTitle:#"Missing Information"
message:#"Make sure you fill out all of the information!"
delegate:nil
cancelButtonTitle:#"ok"
otherButtonTitles:nil] show];
}
return informationComplete;
}
// Sent to the delegate when a PFUser is signed up.
- (void)signUpViewController:(PFSignUpViewController *)signUpController didSignUpUser:(PFUser *)user {
[self dismissModalViewControllerAnimated:YES]; // Dismiss the PFSignUpViewController
}
// Sent to the delegate when the sign up attempt fails.
- (void)signUpViewController:(PFSignUpViewController *)signUpController didFailToSignUpWithError:(NSError *)error {
NSLog(#"Failed to sign up...");
}
// Sent to the delegate when the sign up screen is dismissed.
- (void)signUpViewControllerDidCancelSignUp:(PFSignUpViewController *)signUpController {
NSLog(#"User dismissed the signUpViewController");
}
#end
And here is my .m file for the PFQueryTableViewController subclass:
//
// EventPFQueryTableViewController.m
//
#import "EventPFQueryTableViewController.h"
#import "Event.h"
#interface EventPFQueryTableViewController ()
#end
#implementation EventPFQueryTableViewController
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self)
{
self.parseClassName = kEventListClassKey;
}
return self;
}
- (PFQuery *)queryForTable
{
PFQuery *query = [Event query];
[query whereKey:kEventListFieldKeyUser equalTo:[PFUser currentUser]];
return query;
}
/*
#end
However, the code in the viewDidLoad function of this tab bar view controller does not seem to run before the PFQueryTableViewController (the first view of the root bar view controller) is loaded and attempts to query because I still get the exception:
2014-07-10 20:46:04.226 Plannr[12810:60b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot do a comparison query for type: (null)'
How would I make sure that the log in view loads far before the EventPFQueryTableViewController? Thanks in advance.
First off, you can keep the app from crashing by modifying the - (PFQuery *)queryForTable method of your EventPFQueryTableViewController like this:
if ([PFUser currentUser]) { //User logged in.
PFQuery *query = [Event query];
[query whereKey:kEventListFieldKeyUser equalTo:[PFUser currentUser]];
return query;
}
else {
[super objectsDidLoad:error]; //Return failed to turn off spinner.
return nil;
}
You also want to set a notification listener for logged in event in your PFQueryTableViewController. This will be called when the user logs in to update your tableview call it something like handleLogIn:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleLogIn:) name:kLoggedInEvent object:nil];
In your PFQueryTableViewController, add the handleLogIn method:
- (void)handleLogIn:(NSNotification*) notification {
[self loadObjects]
}
Then when the sign up/log in succeeds, call the notification you created so that TableView gets updated:
[[NSNotificationCenter defaultCenter] postNotificationName:kLoggedInEvent object:nil userInfo:nil];
Hope this helps.
In my app I'm using CLLocationManager and MKMapView. When app launches, I present the user with a disclaimer (once) which has to be accepted. However, when the disclaimer is shown, a popup appears requesting access to the user location.
Is there a way to delay this alertView until the disclaimer is accepted?
Please advice.
EDIT:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if([[NSUserDefaults standardUserDefaults] objectForKey:#"disclaimerAccepted"] == nil) {
[self firstRun];
[[NSUserDefaults standardUserDefaults] synchronize];
}
else
{
[self locationStuff];
}
)
The location alert view is only displayed when you actually request the user's location, so the simplest way to do this would be to ensure that the first time your app is run you start the CLLocationManager or display the MKMapView until after your disclaimer has been accepted. This may require you to move around some methods.
Maybe you need first check for disclaimer and if it accepted start CLLocationManager. If disclaimer not accepted - wait for acceptation and that start CLLocationManager.
- (void)viewDidLoad
{
[super viewDidLoad];
if (disclaimerAccepted)
{
//start CLLocationManager
}
else
{
//Show disclaimer
}
}
- (void)disclaimerAccepted
{
disclaimerAccepted = YES;
//start CLLocationManager
}