I'm looking to use the Facebook Api to check in users in an iOS app.
I was wondering if I have the permissions set in the AppDelegate but want the user to checkin from a different view controller do I have to declare the Facebook instance in every view controller along with the FBRequestDelegate, FBSessionDelegate, FBDialogDelegate delegate methods? or is it a one time thing in the AppDelegate?
thanks for any help.
I just dealt with the exact same problem. Here is my solution:
I Created a FBRequestWrapper which basically contains the following methods:
#import <Foundation/Foundation.h>
#import "Facebook.h"
#define FB_APP_ID #"xx"
#define FB_APP_SECRET #"xx"
#interface FBRequestWrapper : NSObject <FBRequestDelegate, FBSessionDelegate>
{
Facebook *facebook;
BOOL isLoggedIn;
}
#property (nonatomic, retain) Facebook *facebook;
#property (nonatomic, assign) BOOL isLoggedIn;
+ (id) defaultManager;
- (void) setIsLoggedIn:(BOOL) _loggedIn;
- (void) FBSessionBegin:(id) _delegate;
- (void) FBLogout;
- (void) getFBRequestWithGraphPath:(NSString*) _path andDelegate:(id) _delegate;
- (void) sendFBRequestWithGraphPath:(NSString*) _path params:(NSMutableDictionary*) _params andDelegate:(id) _delegate;
#end
So all the Facebook stuff is managed in this Singleton class.
In my AppDelegate I invoke the authentication. Because in my opinion the authentication has to be done before loading all the controllers.
// FACEBOOK
requestWrapper = [FBRequestWrapper defaultManager];
BOOL loggedIn = [requestWrapper isLoggedIn];
// if the user is not currently logged in begin the session
if (!loggedIn) {
[requestWrapper FBSessionBegin:(id)self];
} else {
NSLog(#"Is already logged in!");
}
// Check if the access token is already there. In that case the user is already authenticated with facebook.
// Directly load the controllers and do not wait till facebook has returned back the access_token
if([[NSUserDefaults standardUserDefaults] objectForKey:#"access_token"] != nil &&
[[NSUserDefaults standardUserDefaults] objectForKey:#"exp_date"] != nil) {
[self loadControllers];
}
You can see that I also store the accessToken in the NSUserDefaults space. Because we are using the accessToken as authentication to our webservice.
My AppDelegate method is delegating the FBSessionDelegate method:
#interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate, FBSessionDelegate>
And here is the implementation of the most important method, fbDidLogin:
- (void) fbDidLogin {
NSLog(#"AccessToken: %#", requestWrapper.facebook.accessToken);
[[NSUserDefaults standardUserDefaults] setObject:requestWrapper.facebook.accessToken forKey:#"access_token"];
[[NSUserDefaults standardUserDefaults] setObject:requestWrapper.facebook.expirationDate forKey:#"exp_date"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self loadControllers];
}
Here I store the accesstoken in the NSUserDefaults and load all the Controllers if everything was fine.
Now if you want to access the Facebook Graph Api from any controller outside the AppDelegate you can also use the FBRequestWrapper like that:
- (IBAction)test:(id)sender {
// FBRequestWrapper
NSString *graphPath = #"me/friends";
[[FBRequestWrapper defaultManager] getFBRequestWithGraphPath:graphPath andDelegate:self];
}
Here the code from the FBRequestWrapper.m:
#import "FBRequestWrapper.h"
static FBRequestWrapper *defaultWrapper = nil;
#implementation FBRequestWrapper
#synthesize isLoggedIn, facebook;
+ (id) defaultManager {
if (!defaultWrapper)
defaultWrapper = [[FBRequestWrapper alloc] init];
return defaultWrapper;
}
- (void) setIsLoggedIn:(BOOL) _loggedIn {
isLoggedIn = _loggedIn;
if (isLoggedIn) {
[[NSUserDefaults standardUserDefaults] setObject:facebook.accessToken forKey:#"access_token"];
[[NSUserDefaults standardUserDefaults] setObject:facebook.expirationDate forKey:#"exp_date"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
else {
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"access_token"];
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"exp_date"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
- (void) FBSessionBegin:(id) _delegate {
if (facebook == nil) {
facebook = [[Facebook alloc] initWithAppId:FB_APP_ID andDelegate:_delegate];
[facebook setSessionDelegate:_delegate];
NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:#"access_token"];
NSDate *exp = [[NSUserDefaults standardUserDefaults] objectForKey:#"exp_date"];
if (token != nil && exp != nil && [token length] > 2) {
isLoggedIn = YES;
facebook.accessToken = token;
facebook.expirationDate = [NSDate distantFuture];
[self setIsLoggedIn:isLoggedIn];
NSLog(#"Access token: %#", facebook.accessToken);
}
}
if(![facebook isSessionValid]) {
NSArray *permissions = [NSArray arrayWithObjects:#"offline_access", #"read_friendlists", #"user_about_me", nil];
// if no session is available login
[facebook authorize:permissions];
}
}
- (void) FBLogout {
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"access_token"];
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"exp_date"];
[[NSUserDefaults standardUserDefaults] synchronize];
[facebook logout:self];
}
// Make simple requests
- (void) getFBRequestWithGraphPath:(NSString*) _path andDelegate:(id) _delegate {
if (_path != nil) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
if (_delegate == nil)
_delegate = self;
if(isLoggedIn) {
NSLog(#"is logged in in the get method");
} else {
NSLog(#"Is NOT logged in the get metthod");
}
[facebook requestWithGraphPath:_path andDelegate:_delegate];
}
}
// Used for publishing
- (void) sendFBRequestWithGraphPath:(NSString*) _path params:(NSMutableDictionary*) _params andDelegate:(id) _delegate {
if (_delegate == nil)
_delegate = self;
if (_params != nil && _path != nil) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[facebook requestWithGraphPath:_path andParams:_params andHttpMethod:#"POST" andDelegate:_delegate];
}
}
#pragma mark -
#pragma mark FacebookSessionDelegate
- (void)fbDidLogin {
isLoggedIn = YES;
[[NSUserDefaults standardUserDefaults] setObject:facebook.accessToken forKey:#"access_token"];
[[NSUserDefaults standardUserDefaults] setObject:facebook.expirationDate forKey:#"exp_date"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)fbDidNotLogin:(BOOL)cancelled {
isLoggedIn = NO;
}
- (void)fbDidLogout {
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"access_token"];
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"exp_date"];
[[NSUserDefaults standardUserDefaults] synchronize];
isLoggedIn = NO;
}
#pragma mark -
#pragma mark FBRequestDelegate
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
//NSLog(#"ResponseFailed: %#", error);
}
- (void)request:(FBRequest *)request didLoad:(id)result {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
//NSLog(#"Parsed Response: %#", result);
}
/**
* Called after the access token was extended. If your application has any
* references to the previous access token (for example, if your application
* stores the previous access token in persistent storage), your application
* should overwrite the old access token with the new one in this method.
* See extendAccessToken for more details.
*/
- (void)fbDidExtendToken:(NSString*)accessToken expiresAt:(NSDate*)expiresAt {
NSLog(#"Fb did extend token.. NOT IMPLEMENTED YET!");
}
/**
* Called when the current session has expired. This might happen when:
* - the access token expired
* - the app has been disabled
* - the user revoked the app's permissions
* - the user changed his or her password
*/
- (void)fbSessionInvalidated {
NSLog(#"Fb session invalidated.. NOT IMPLEMENTED YET!");
}
#end
Related
I'm utilizing AVAudioPlayer in MyScene - the main game scene in my Sprite Kit game. I also have an OptionsScene containing 2 buttons. 1 of the buttons is for switching music ON/OFF.
Everything works except pressing the button doesn't instantly turn music ON or OFF. I have to leave the app, double-tap iPhone's Home button & swipe-up the game View to turn it off completely.
Once that is done, I can open the game & the previously selected ON/OFF button takes effect. Is there a way to have AVAudioPlayer turn off instantly upon clicking it's button in the options scene?
I do use a custom class for playing the music that I did not write. So I don't completely understand how it works. Here is the method I use inside MyScene to activate the AVAudioPlayer:
-(void)musicPlayer
{
defaults = [NSUserDefaults standardUserDefaults]; //"Activate" defaults.
music = [defaults boolForKey:#"MusicButtonKEY"]; //Then assign that defaults to a BOOL (music in this case).
if (music == YES) //Check what defaults BOOL key value was set to & act accordingly to that.
{
[self.audioController tryPlayMusic]; //tryPlayMusic is a method from AudioController class.
NSLog(#"Music playing");
}
else /*if (music == NO)*/
{
[self.audioController stopPlayingMusic]; //stopPlayingMusic is a method from AudioController class.
NSLog(#"Music not playing");
}
}
Here is the .h & .m files of the AudioController class:
AudioController.h
#import <Foundation/Foundation.h>
#interface AudioController : NSObject
-(instancetype)init;
-(void)tryPlayMusic;
-(void)stopPlayingMusic;
#end
AudioController.m
#import "AudioController.h"
#import AVFoundation;
#interface AudioController () <AVAudioPlayerDelegate>
#property (strong, nonatomic) AVAudioSession *audioSession;
#property (strong, nonatomic) AVAudioPlayer *backgroundMusicPlayer;
#property (assign) BOOL backgroundMusicPlaying;
#property (assign) BOOL backgroundMusicInterrupted;
#end
#implementation AudioController
#pragma mark - Public
-(instancetype)init
{
self = [super init];
if (self)
{
[self configureAudioSession];
[self configureAudioPlayer];
}
return self;
}
-(void)tryPlayMusic
{
//If background music or other music is already playing, nothing more to do here
if (self.backgroundMusicPlaying || [self.audioSession isOtherAudioPlaying])
{
return;
}
// Play background music if no other music is playing and we aren't playing already
//Note: prepareToPlay preloads the music file and can help avoid latency. If you don't
//call it, then it is called anyway implicitly as a result of [self.backgroundMusicPlayer play];
//It can be worthwhile to call prepareToPlay as soon as possible so as to avoid needless
//delay when playing a sound later on.
[self.backgroundMusicPlayer prepareToPlay];
[self.backgroundMusicPlayer play];
self.backgroundMusicPlaying = YES;
}
-(void)stopPlayingMusic
{
if (self.backgroundMusicPlaying == YES)
{
[self.backgroundMusicPlayer stop];
self.backgroundMusicPlaying = NO;
}
}
#pragma mark - Private
-(void)configureAudioSession
{
//Implicit initialization of audio session.
self.audioSession = [AVAudioSession sharedInstance];
//Set category of audio session
//See handy chart on pg. 46 of the Audio Session Programming Guide for what the categories mean
//Not absolutely required in this example, but good to get into the habit of doing
//See pg. 10 of Audio Session Programming Guide for "Why a Default Session Usually Isn't What You Want"
NSError *setCategoryError = nil;
if ([self.audioSession isOtherAudioPlaying])
{ //Mix sound effects with music already playing
[self.audioSession setCategory:AVAudioSessionCategorySoloAmbient error:&setCategoryError];
self.backgroundMusicPlaying = NO;
}
else
{
[self.audioSession setCategory:AVAudioSessionCategoryAmbient error:&setCategoryError];
}
if (setCategoryError)
{
NSLog(#"Error setting category! %ld", (long)[setCategoryError code]);
}
}
-(void)configureAudioPlayer
{
//Create audio player with background music.
NSString *backgroundMusicPath = [[NSBundle mainBundle] pathForResource:#"ThemeA" ofType:#"mp3"];
NSURL *backgroundMusicURL = [NSURL fileURLWithPath:backgroundMusicPath];
self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:nil];
self.backgroundMusicPlayer.delegate = self; //We need this so we can restart after interruptions
self.backgroundMusicPlayer.numberOfLoops = -1; //Negative number means loop forever
}
#pragma mark - AVAudioPlayerDelegate methods
-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player
{
//It is often not necessary to implement this method since by the time
//this method is called, the sound has already stopped. You don't need to
//stop it yourself.
//In this case the backgroundMusicPlaying flag could be used in any
//other portion of the code that needs to know if your music is playing.
self.backgroundMusicInterrupted = YES;
self.backgroundMusicPlaying = NO;
}
-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flag
{
//Since this method is only called if music was previously interrupted, you know that the music has stopped playing & can now be resumed.
[self tryPlayMusic];
self.backgroundMusicInterrupted = NO;
}
#end
Here is the button code from OptionsScene:
#import "OptionsScene.h"
#import "ViewController.h"
BOOL soundON;
BOOL musicON;
#interface OptionsScene ()
#property (strong, nonatomic) AGSpriteButton *AGSoundButton;
#property (strong, nonatomic) AGSpriteButton *AGMusicButton;
#end
#implementation OptionsScene
{
NSUserDefaults *defaults;
}
-(id)initWithSize:(CGSize)size
{
self = [super initWithSize:size];
{
defaults = [NSUserDefaults standardUserDefaults];
[self background]; //Won't show code for this method since it just loads the background.
//Background music & sound dispatched once to get things going in this scene so to speak.
static dispatch_once_t onceOS;
dispatch_once(&onceOS, ^ {
//Code to run once
soundON = YES;
musicON = YES;
[defaults setBool:YES forKey:#"SoundButtonKEY"];
[defaults synchronize];
self.AGSoundButton.texture = [SKTexture textureWithImageNamed:#"checkboxYES"];
NSLog(#"SOUND switched to ON");
[defaults setBool:YES forKey:#"MusicButtonKEY"];
[defaults synchronize];
self.AGMusicButton.texture = [SKTexture textureWithImageNamed:#"checkboxYES"];
NSLog(#"MUSIC switched to ON");
});
}
return self;
}
-(void)update:(NSTimeInterval)currentTime
{
}
-(void)didMoveToView:(SKView *)view
{
//Sound button.
self.AGSoundButton = [AGSpriteButton buttonWithImageNamed:#"checkboxYES"];
self.AGSoundButton.position = CGPointMake(self.size.width/2 + 50, self.size.height/2 + 70);
self.AGSoundButton.zPosition = 2.2;
[self.AGSoundButton addTarget:self selector:#selector(soundInit) withObject:[NSValue valueWithCGPoint:CGPointMake(self.size.width + 230, self.size.height/2 + 3)] forControlEvent:AGButtonControlEventTouchUpInside];
[self addChild:self.AGSoundButton];
//Music button.
self.AGMusicButton = [AGSpriteButton buttonWithImageNamed:#"checkboxYES"];
self.AGMusicButton.position = CGPointMake(self.size.width/2 + 50, self.size.height/2 + 10);
self.AGMusicButton.zPosition = 2.2;
[self.AGMusicButton addTarget:self selector:#selector(musicInit) withObject:[NSValue valueWithCGPoint:CGPointMake(self.size.width + 230, self.size.height/2 + 3)] forControlEvent:AGButtonControlEventTouchUpInside];
[self addChild:self.AGMusicButton];
//Call upon the saved BOOL/Button positions from their respective key's.
BOOL soundDefaults = [defaults boolForKey:#"SoundButtonKEY"];
BOOL musicDefaults = [defaults boolForKey:#"MusicButtonKEY"];
//Sound button retrieval.
if (soundDefaults == YES)
{
self.AGSoundButton.texture = [SKTexture textureWithImageNamed:#"checkboxYES"];
NSLog(#"Sound is ON");
}
if (soundDefaults == NO)
{
self.AGSoundButton.texture = [SKTexture textureWithImageNamed:#"checkboxNO"];
NSLog(#"Sound is OFF");
}
//Music button retrieval.
if (musicDefaults == YES)
{
self.AGMusicButton.texture = [SKTexture textureWithImageNamed:#"checkboxYES"];
NSLog(#"Music is ON");
}
if (musicDefaults == NO)
{
self.AGMusicButton.texture = [SKTexture textureWithImageNamed:#"checkboxNO"];
NSLog(#"Music is OFF");
}
}
//Sound button switch ON/OFF.
-(void)soundInit
{
if (soundON == YES)
{
soundON = NO;
[defaults setBool:NO forKey:#"SoundButtonKEY"];
[defaults synchronize];
self.AGSoundButton.texture = [SKTexture textureWithImageNamed:#"checkboxNO"];
NSLog(#"SOUND switched to OFF");
}
else /*if (soundON == NO)*/
{
soundON = YES;
[defaults setBool:YES forKey:#"SoundButtonKEY"];
[defaults synchronize];
self.AGSoundButton.texture = [SKTexture textureWithImageNamed:#"checkboxYES"];
NSLog(#"SOUND switched to ON");
}
}
//Music button switch ON/OFF.
-(void)musicInit
{
if (musicON == YES)
{
musicON = NO;
[defaults setBool:NO forKey:#"MusicButtonKEY"];
[defaults synchronize];
self.AGMusicButton.texture = [SKTexture textureWithImageNamed:#"checkboxNO"];
NSLog(#"MUSIC switched to OFF");
}
else /*if (musicON == NO)*/
{
musicON = YES;
[defaults setBool:YES forKey:#"MusicButtonKEY"];
[defaults synchronize];
self.AGMusicButton.texture = [SKTexture textureWithImageNamed:#"checkboxYES"];
NSLog(#"MUSIC switched to ON");
}
}
//Brings back to GameScene. Called by a 3rd button that I didn't bother including in this code. It doesn't really matter.
-(void)backToGame
{
NSLog(#"Going back");
[defaults synchronize]; //Save button states, just in case, before leaving scene.
//Brings back to game scene.
MyScene *firstScene = [MyScene sceneWithSize:self.size];
[self.view presentScene:firstScene];
}
#end
Really hope this helps with the details.
So I've been researching on how to make a login view controller as the initial view controller instead of the splitview.
Some of the answers I've seen would recommend a modal view to be loaded? I'm not sure how that is set up.
eg.
How to add a login view before a UISplitViewController iPad
and
How to implement SplitViewController on second level.?
So do I add those on the loginviewcontroller class? Or where?
Any advice is welcome.
Thanks!!
I've done this by creating two storyboards: one with the (full-screen) login and one with the split-view.
To switch between them, I've added a custom protocol:
#import <Foundation/Foundation.h>
#protocol RootViewControllerDelegate <NSObject>
-(void)switchToStoryboard: (UIStoryboard *) storyboad animationDirectionOrNil: (NSString *)direction;
#end
The AppDelegate then implements this protocol:
-(void)switchToStoryboard:(id)storyboad animationDirectionOrNil:(NSString *)direction {
UIViewController *newRoot=[storyboad instantiateInitialViewController];
if ([newRoot respondsToSelector:#selector(setRootViewControllerDelegate:)]) {
[newRoot setRootViewControllerDelegate:self];
}
self.window.rootViewController=newRoot;
if(direction){
CATransition* transition=[CATransition animation];
transition.type=kCATransitionPush;
transition.subtype=direction;
[self.window.layer addAnimation:transition forKey:#"push_transition"];
}
}
As you can see, it tries to set itself as the delegate again, so the other view-controller can switch back or to another storyboard. In order for this to work, you would have to subclass UISplitView:
Header
#import <UIKit/UIKit.h>
#import "RootViewControllerDelegate.h"
#interface MySplitViewController : UISplitViewController
#property (nonatomic, weak) id <RootViewControllerDelegate> rootViewControllerDelegate;
#end
iMplementation
#import "MySplitViewController.h"
#implementation MySplitViewController
#synthesize rootViewControllerDelegate;
- (void)viewDidLoad
{
[super viewDidLoad];
for (UIViewController *viewController in self.viewControllers) {
if ([viewController respondsToSelector:#selector(setRootViewControllerDelegate:)]) {
[viewController setRootViewControllerDelegate:self.rootViewControllerDelegate];
}
}
}
#end
This simple implementation looks for child-view-controllers that accept a root-view-controller-delegate and hands it down. So when you want to add a "Show Login"-button to a certain (master- or detail-)view, just create your own UIViewController-subclass, add a #property id<RootViewControllerDelegate> rootViewControllerDelegate and associate an action like this with the button:
- (IBAction)loginButtonClicked:(id)sender {
UIStoryboard *mainSB=[UIStoryboard storyboardWithName:#"LoginStoryboard" bundle:nil];
NSString *animationDirection=kCATransitionFromTop;
UIDeviceOrientation currentOrientation=[[UIDevice currentDevice] orientation];
if (currentOrientation==UIDeviceOrientationLandscapeLeft) {
animationDirection=kCATransitionFromBottom;
}
[self.rootViewControllerDelegate switchToStoryboard:mainSB animationDirectionOrNil:animationDirection];
}
Feel free to adjust everything to your needs.
well here it is my friend. i created a ibaction in in btn and pushed the new view with modal option of the story board. them i plugged in the classes for the login view which also refers to constants that keeps the record strait. then after login is recognized i pushed a new view. bare in mind i was having the users create a password in their device and not importing it from the server. if you want to import it from the server it will be different.
here is the log in .h
#import <UIKit/UIKit.h>
#import "Constants.h"
#interface LogInViewController : UIViewController<UITextFieldDelegate>
#property (nonatomic) BOOL pinValidated;
#end
and here is the code for login .m
#import "LogInViewController.h"
#import "KeychainWrapper.h"
#interface LogInViewController ()
#end
#implementation LogInViewController
#synthesize pinValidated;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
// Helper method to congregate the Name and PIN fields for validation.
- (BOOL)credentialsValidated
{
NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
BOOL pin = [[NSUserDefaults standardUserDefaults] boolForKey:PIN_SAVED];
if (name && pin) {
return YES;
} else {
return NO;
}
}
- (void)presentAlertViewForPassword
{
// 1
BOOL hasPin = [[NSUserDefaults standardUserDefaults] boolForKey:PIN_SAVED];
// 2
if (hasPin) {
// 3
NSString *user = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
NSString *message = [NSString stringWithFormat:#"What is %#'s password?", user];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Enter Password"
message:message
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Done", nil];
// 4
[alert setAlertViewStyle:UIAlertViewStyleSecureTextInput]; // Gives us the password field
alert.tag = kAlertTypePIN;
// 5
UITextField *pinField = [alert textFieldAtIndex:0];
pinField.delegate = self;
pinField.autocapitalizationType = UITextAutocapitalizationTypeWords;
pinField.tag = kTextFieldPIN;
[alert show];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Setup Credentials"
message:#"Enter Your information!"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Done", nil];
// 6
[alert setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];
alert.tag = kAlertTypeSetup;
UITextField *nameField = [alert textFieldAtIndex:0];
nameField.autocapitalizationType = UITextAutocapitalizationTypeWords;
nameField.placeholder = #"Name"; // Replace the standard placeholder text with something more applicable
nameField.delegate = self;
nameField.tag = kTextFieldName;
UITextField *passwordField = [alert textFieldAtIndex:1]; // Capture the Password text field since there are 2 fields
passwordField.delegate = self;
passwordField.tag = kTextFieldPassword;
[alert show];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (alertView.tag == kAlertTypePIN) {
if (buttonIndex == 1 && self.pinValidated) { // User selected "Done"
[self performSegueWithIdentifier:#"ScoutSegue" sender:self];
self.pinValidated = NO;
} else { // User selected "Cancel"
[self presentAlertViewForPassword];
}
} else if (alertView.tag == kAlertTypeSetup) {
if (buttonIndex == 1 && [self credentialsValidated]) { // User selected "Done"
[self performSegueWithIdentifier:#"ScoutSegue" sender:self];
} else { // User selected "Cancel"
[self presentAlertViewForPassword];
}
}
}
#pragma mark - Text Field + Alert View Methods
- (void)textFieldDidEndEditing:(UITextField *)textField
{
// 1
switch (textField.tag) {
case kTextFieldPIN: // We go here if this is the 2nd+ time used (we've already set a PIN at Setup).
NSLog(#"User entered PIN to validate");
if ([textField.text length] > 0) {
// 2
NSUInteger fieldHash = [textField.text hash]; // Get the hash of the entered PIN, minimize contact with the real password
// 3
if ([KeychainWrapper compareKeychainValueForMatchingPIN:fieldHash]) { // Compare them
NSLog(#"** User Authenticated!!");
self.pinValidated = YES;
} else {
NSLog(#"** Wrong Password :(");
self.pinValidated = NO;
}
}
break;
case kTextFieldName: // 1st part of the Setup flow.
NSLog(#"User entered name");
if ([textField.text length] > 0) {
[[NSUserDefaults standardUserDefaults] setValue:textField.text forKey:USERNAME];
[[NSUserDefaults standardUserDefaults] synchronize];
}
break;
case kTextFieldPassword: // 2nd half of the Setup flow.
NSLog(#"User entered PIN");
if ([textField.text length] > 0) {
NSUInteger fieldHash = [textField.text hash];
// 4
NSString *fieldString = [KeychainWrapper securedSHA256DigestHashForPIN:fieldHash];
NSLog(#"** Password Hash - %#", fieldString);
// Save PIN hash to the keychain (NEVER store the direct PIN)
if ([KeychainWrapper createKeychainValue:fieldString forIdentifier:PIN_SAVED]) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:PIN_SAVED];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"** Key saved successfully to Keychain!!");
}
}
break;
default:
break;
}
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.pinValidated = NO;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self presentAlertViewForPassword];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
here is the code for the constant
// Used for saving to NSUserDefaults that a PIN has been set, and is the unique identifier for the Keychain.
#define PIN_SAVED #"hasSavedPIN"
// Used for saving the user's name to NSUserDefaults.
#define USERNAME #"username"
// Used to specify the application used in accessing the Keychain.
#define APP_NAME [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"]
// Used to help secure the PIN.
// Ideally, this is randomly generated, but to avoid the unnecessary complexity and overhead of storing the Salt separately, we will standardize on this key.
// !!KEEP IT A SECRET!!
#define SALT_HASH #"FvTivqTqZXsgLLx1v3P8TGRyVHaSOB1pvfm02wvGadj7RLHV8GrfxaZ84oGA8RsKdNRpxdAojXYg9iAj"
// Typedefs just to make it a little easier to read in code.
typedef enum {
kAlertTypePIN = 0,
kAlertTypeSetup
} AlertTypes;
typedef enum {
kTextFieldPIN = 1,
kTextFieldName,
kTextFieldPassword
} TextFieldTypes;
here is the keychainwrapper
#import <Foundation/Foundation.h>
#import <Security/Security.h>
#import <CommonCrypto/CommonHMAC.h>
#interface KeychainWrapper : NSObject
// Generic exposed method to search the keychain for a given value. Limit one result per search.
+ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier;
// Calls searchKeychainCopyMatchingIdentifier: and converts to a string value.
+ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier;
// Simple method to compare a passed in hash value with what is stored in the keychain.
// Optionally, we could adjust this method to take in the keychain key to look up the value.
+ (BOOL)compareKeychainValueForMatchingPIN:(NSUInteger)pinHash;
// Default initializer to store a value in the keychain.
// Associated properties are handled for you - setting Data Protection Access, Company Identifer (to uniquely identify string, etc).
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier;
// Updates a value in the keychain. If you try to set the value with createKeychainValue: and it already exists,
// this method is called instead to update the value in place.
+ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier;
// Delete a value in the keychain.
+ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier;
// Generates an SHA256 (much more secure than MD5) hash.
+ (NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash;
+ (NSString*)computeSHA256DigestForString:(NSString*)input;
#end
and finally here is the code for the keychainwrapper .m
#import "KeychainWrapper.h"
#import "Constants.h"
#implementation KeychainWrapper
// *** NOTE *** This class is ARC compliant - any references to CF classes must be paired with a "__bridge" statement to
// cast between Objective-C and Core Foundation Classes. WWDC 2011 Video "Introduction to Automatic Reference Counting" explains this.
// *** END NOTE ***
+ (NSMutableDictionary *)setupSearchDirectoryForIdentifier:(NSString *)identifier {
// Setup dictionary to access keychain.
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
// Specify we are using a password (rather than a certificate, internet password, etc).
[searchDictionary setObject:( id)kSecClassGenericPassword forKey:( id)kSecClass];
// Uniquely identify this keychain accessor.
[searchDictionary setObject:APP_NAME forKey:( id)kSecAttrService];
// Uniquely identify the account who will be accessing the keychain.
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:( id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:( id)kSecAttrAccount];
return searchDictionary;
}
+ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier
{
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
// Limit search results to one.
[searchDictionary setObject:( id)kSecMatchLimitOne forKey:( id)kSecMatchLimit];
// Specify we want NSData/CFData returned.
[searchDictionary setObject:( id)kCFBooleanTrue forKey:( id)kSecReturnData];
// Search.
NSData *result = nil;
CFTypeRef foundDict = NULL;
OSStatus status = SecItemCopyMatching(( CFDictionaryRef)searchDictionary, &foundDict);
if (status == noErr) {
result = ( NSData *)foundDict;
} else {
result = nil;
}
return result;
}
+ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier
{
NSData *valueData = [self searchKeychainCopyMatchingIdentifier:identifier];
if (valueData) {
NSString *value = [[NSString alloc] initWithData:valueData
encoding:NSUTF8StringEncoding];
return value;
} else {
return nil;
}
}
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
{
NSMutableDictionary *dictionary = [self setupSearchDirectoryForIdentifier:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:valueData forKey:( id)kSecValueData];
// Protect the keychain entry so it's only valid when the device is unlocked.
[dictionary setObject:( id)kSecAttrAccessibleWhenUnlocked forKey:( id)kSecAttrAccessible];
// Add.
OSStatus status = SecItemAdd(( CFDictionaryRef)dictionary, NULL);
// If the addition was successful, return. Otherwise, attempt to update existing key or quit (return NO).
if (status == errSecSuccess) {
return YES;
} else if (status == errSecDuplicateItem){
return [self updateKeychainValue:value forIdentifier:identifier];
} else {
return NO;
}
}
+ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
{
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[updateDictionary setObject:valueData forKey:( id)kSecValueData];
// Update.
OSStatus status = SecItemUpdate(( CFDictionaryRef)searchDictionary,
( CFDictionaryRef)updateDictionary);
if (status == errSecSuccess) {
return YES;
} else {
return NO;
}
}
+ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier
{
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
CFDictionaryRef dictionary = ( CFDictionaryRef)searchDictionary;
//Delete.
SecItemDelete(dictionary);
}
+ (BOOL)compareKeychainValueForMatchingPIN:(NSUInteger)pinHash
{
if ([[self keychainStringFromMatchingIdentifier:PIN_SAVED] isEqualToString:[self securedSHA256DigestHashForPIN:pinHash]]) {
return YES;
} else {
return NO;
}
}
// This is where most of the magic happens (the rest of it happens in computeSHA256DigestForString: method below).
// Here we are passing in the hash of the PIN that the user entered so that we can avoid manually handling the PIN itself.
// Then we are extracting the username that the user supplied during setup, so that we can add another unique element to the hash.
// From there, we mash the user name, the passed-in PIN hash, and the secret key (from ChristmasConstants.h) together to create
// one long, unique string.
// Then we send that entire hash mashup into the SHA256 method below to create a "Digital Digest," which is considered
// a one-way encryption algorithm. "One-way" means that it can never be reverse-engineered, only brute-force attacked.
// The algorthim we are using is Hash = SHA256(Name + Salt + (Hash(PIN))). This is called "Digest Authentication."
+ (NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash
{
// 1
NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
name = [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// 2
NSString *computedHashString = [NSString stringWithFormat:#"%#%i%#", name, pinHash, SALT_HASH];
// 3
NSString *finalHash = [self computeSHA256DigestForString:computedHashString];
NSLog(#"** Computed hash: %# for SHA256 Digest: %#", computedHashString, finalHash);
return finalHash;
}
// This is where the rest of the magic happens.
// Here we are taking in our string hash, placing that inside of a C Char Array, then parsing it through the SHA256 encryption method.
+ (NSString*)computeSHA256DigestForString:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
// This is an iOS5-specific method.
// It takes in the data, how much data, and then output format, which in this case is an int array.
CC_SHA256(data.bytes, data.length, digest);
// Setup our Objective-C output.
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
// Parse through the CC_SHA256 results (stored inside of digest[]).
for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[output appendFormat:#"%02x", digest[i]];
}
return output;
}
#end
this is the blueprint for creating a login view before any other view weather it be view controller or any other view such as tab bar or so. take a good look at this set of codes and modify them as you please. hope this helps you my friend. the codes are there all you have to do is study them and modify them to what you want. happy coding.
I have had the Facebook SDK working in the APPDelegate as instructed by the Facebook tutorial, however I am trying to put in into a singleton method. Every tutorial i've found seems to be for an older version of the SDK, but I have managed success with this one http://indiedevstories.com/2011/08/30/a-facebook-reusable-class/
I'm having 2 problems, the first is posted here this is the second:
I want a button to post to Facebook, but if the user isn't logged in then they need to login first then post (without having to press a separate login button first).
I can log in fine, and I can post fine, however I can't do both together.
If not logged in, the post code shows the login screen, but doesn't goto the post screen after logging in. you have to press post again.
If you try to login but are already logged in, then nothing happens.
So on the button I use code to login then post, as the login is just skipped if already logged in.
The problem i'm having, is the post code is being run instantly after the login code, so before the user has had a chance to login. This results in 2 popups being opened (1 login and 1 post which displays login as not logged in yet).
How can I get my code to wait for the user to login before moving onto the next line in the code to post?
FacebookHelper.h
#interface FacebookHelper : NSObject <FBSessionDelegate, FBRequestDelegate, FBDialogDelegate, FBLoginDialogDelegate> {
Facebook *_facebook;
NSArray *_permissions;
}
#property (nonatomic,strong) Facebook *facebook;
+(FacebookHelper *) sharedInstance;
+(void)fbDidLogin;
#pragma mark - Public Methods
-(BOOL) isFBSessionValid;
-(void) login;
-(void) logout;
-(void) postToWallWithDialogNewHighscore:(int)highscore;
#end
FacebookHelper.m
#implementation FacebookHelper
#synthesize facebook;
#pragma mark -
#pragma mark Singleton Variables
static FacebookHelper *singletonDelegate = nil;
#pragma mark -
#pragma mark Singleton Methods
+(FacebookHelper *)sharedInstance
{
#synchronized(self) {
if (singletonDelegate == nil) {
singletonDelegate = [[self alloc] init]; // Assignment not done here
}
}
return singletonDelegate;
}
-(id)init
{
self = [super init];
if (self) {
_facebook = [[Facebook alloc] initWithAppId:kAppId andDelegate:self];
// Restore previous session
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"] && [defaults objectForKey:#"FBExpirationDateKey"]) {
_facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
_facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
//
}
return self;
}
+(id)allocWithZone:(NSZone *)zone
{
#synchronized(self) {
if (singletonDelegate == nil) {
singletonDelegate = [super allocWithZone:zone];
// assignment and return on first allocation
return singletonDelegate;
}
}
//on subsequent allocation attemps, return nil
return nil;
}
-(id)copyWithZone:(NSZone *)zone
{
return self;
}
#pragma mark - Facebook Delegate Methods
-(void)fbDidLogin
{
NSLog(#"fbDidLogin");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[_facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[_facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
-(void)fbDidLogout
{
NSLog(#"fbDidLogout");
// Remove saved authorisation information if it exists
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]) {
[defaults removeObjectForKey:#"FBAccessTokenKey"];
[defaults removeObjectForKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
}
#pragma mark - Public Methods
-(NSMutableDictionary *) buildPostParamsWithHighScore:(int)highscore
{
NSString *customMessage = [NSString stringWithFormat:kCustomMessage, highscore, kAppName];
NSString *postName = kAppName;
NSString *serverLink = [NSString stringWithFormat:kServerLink];
NSString *imageSrc = kImageScr;
//Final params build
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
//#"message", #"message",
imageSrc, #"picture",
serverLink, #"link",
postName, #"name",
#" ", #"caption",
customMessage, #"description",
nil];
return params;
}
-(BOOL) isFBSessionValid
{
// Check if there is a valid session
//_facebook = [[Facebook alloc] initWithAppId:kAppId andDelegate:self];
_facebook.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:#"FBAccessTokenKey"];
_facebook.expirationDate = [[NSUserDefaults standardUserDefaults] objectForKey:#"FBExpirationDateKey"];
NSLog(#"accessToken=%# expirationDaate=%#",_facebook.accessToken,_facebook.expirationDate);
if (![_facebook isSessionValid]) {
NSLog(#"FacebookHelper isFBSessionValid = NO");
return NO;
} else {
NSLog(#"FacebookHelper isFBSessionValid = YES");
return YES;
}
return NO;
}
-(void) login
{
NSLog(#"FacebookHelper login");
_permissions = [NSArray arrayWithObjects:#"publish_stream", nil]; //#"read_stream", #"offline_access"
[_facebook authorize:_permissions];
}
-(void) logout
{
[_facebook logout];
}
-(void) postToWallWithDialogNewHighscore:(int)highscore
{
NSMutableDictionary *params = [self buildPostParamsWithHighScore:highscore];
NSLog(#"Post Feed");
[_facebook dialog:#"feed" andParams:params andDelegate:self];
}
#end
Button Action:
- (IBAction)facebookTest:(id)sender {
[[FacebookHelper sharedInstance] login];
[[FacebookHelper sharedInstance] postToWallWithDialogNewHighscore:123];
}
I have updated the singleton class to post scores to Facebook Wall. Check out the new version: http://indiedevstories.com/2012/04/11/facebookscorer-post-highscores-to-users-facebook-wall/
This new version handles correctly the internal state when authorization and login is required.
HTH
What you need to do is have your postToWallWithDialogNewHighscore call the login, if necessary, and then wait to hear back from the FBSession delegate. Then you need to setup your facebook fbDidLogin delegate so that when it is done it sends back a message. There are a number of ways to do this (a post-login selector, NSNotificationCenter, etc). Something like this should work (note I am assuming an ivar of NSMutableDictionary *postParams for convenience):
-(void)doPost {
NSLog(#"Post Feed");
[_facebook dialog:#"feed" andParams:postParams andDelegate:self];
}
-(void)postToWallWithDialogNewHighscore:(int)highscore
{
postParams = [self buildPostParamsWithHighScore:highscore];
if (_facebook.isSessionValid) {
[self doPost];
} else {
//register an observer for FB login messages
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(postToWallObserver:) name:#"FBLoginComplete" object:nil];
[self login];
}
}
-(void)fbDidLogin {
NSLog(#"token granted until %#", [_facebook expirationDate]);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[_facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[_facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:#"FBLoginComplete" object:nil];
}
-(void)fbDidNotLogin:(BOOL)cancelled {
//do nothing as they didn't login
}
-(void) postToWallObserver:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"FBLoginComplete"]) {
if ([_facebook isSessionValid]) {
[self doPost];
}
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Note: I have not compiled this code so there may be typos, etc.
I have created a singleton class called "LoginFacebook" which is meant to connect user to Facebook and to perform the different request. The problem is that I get an error about the access_token. Here it is :
3 : <CFString 0x4c552f0 [0xe50400]>{contents = "message"} = <CFString 0x4c55250 [0xe50400]>{contents = "An active access token must be used to query information about the current user."}
First I connect to Facebook by making the following request from another View Controller :
[[LoginFacebook loginFacebook] launchFacebook:self]
Then I make that second request from the same other View Controller but from another method :
[[LoginFacebook loginFacebook] requireName:self]
Here is my singleton class "LoginFacebook" :
LoginFacebook.h :
#import <UIKit/UIKit.h>
#import "LoginFacebook.h"
#interface FirstViewController : UIViewController {
}
-(IBAction)performConnect:(id)sender;
-(IBAction)performName:(id)sender;
#end
LoginFacebook.m :
#import "LoginFacebook.h"
static LoginFacebook *loginFacebook = nil;
#implementation LoginFacebook
#synthesize name;
#synthesize facebook;
-(void)launchFacebook:(id)sender
{
permissions = [[NSArray arrayWithObjects:
#"read_stream", #"publish_stream", #"offline_access",nil] retain];
Facebook* facebookbis = [[Facebook alloc] initWithAppId:#"168938499825684"];
facebook = facebookbis;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"]
&& [defaults objectForKey:#"FBExpirationDateKey"]) {
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
if (![facebook isSessionValid]) {
[facebook authorize:nil delegate:self];
}
}
-(NSString *)requireName:(id)sender
{
NSLog(#"requireName asked");
[facebook requestWithGraphPath:#"me" andDelegate:self];
return name;
NSLog(#"%#",[facebook accessToken]);
}
+ (LoginFacebook *)loginFacebook
{
if (loginFacebook == nil) {
loginFacebook = [[super allocWithZone:NULL] init];
}
return loginFacebook;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self loginFacebook] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
// FBRequestDelegate
/**
* Called when the Facebook API request has returned a response. This callback
* gives you access to the raw response. It's called before
* (void)request:(FBRequest *)request didLoad:(id)result,
* which is passed the parsed response object.
*/
- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response {
NSLog(#"received response");
}
/**
* Called when a request returns and its response has been parsed into
* an object. The resulting object may be a dictionary, an array, a string,
* or a number, depending on the format of the API response. If you need access
* to the raw response, use:
*
* (void)request:(FBRequest *)request
* didReceiveResponse:(NSURLResponse *)response
*/
- (void)request:(FBRequest *)request didLoad:(id)result {
if ([result isKindOfClass:[NSArray class]]) {
result = [result objectAtIndex:0];
}
name = [result objectForKey:#"name"];
NSLog(#"request didLoad");
};
/**
* Called when an error prevents the Facebook API request from completing
* successfully.
*/
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
name = [error localizedDescription];
NSLog(#"----request didFailWithError");
NSLog(#"%#", [error localizedDescription]);
NSLog(#"%#", [error description]);
};
////////////////////////////////////////////////////////////////////////////////
// FBDialogDelegate
/**
* Called when a UIServer Dialog successfully return.
*/
- (void)dialogDidComplete:(FBDialog *)dialog {
name = #"publish successfully";
}
#end
Please also note that I added the following method (with the corresponding FacebookLogin *facebook in the .h) to my App Delegate :
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
return [facebook handleOpenURL:url];
}
Does one of you know what is going wrong there ? I have been struggling with the code for 2 days now...
From the error message it seems your access token isn't valid anymore or you didn't even have an access token yet. Does your app actually open a web browser the first time a user is trying to access Facebook? If not, then you probably failed to configure the project properly.
Perhaps it'd be a good idea to share the code of my Facebook singleton - I believe the code is pretty clean and easy to understand & expand. Since my needs are currently very modest I only have a method to authorize (login) and another method to post to wall. I'm using a stack so I can perform some operations in the correct order (for example login before posting message to wall, if user isn't logged in yet).
SDFacebookController.h
#import <Foundation/Foundation.h>
#import "FBConnect.h"
#interface SDFacebookController : NSObject
<FBSessionDelegate,
FBRequestDelegate>
#property (nonatomic, retain) Facebook *facebook;
+ (SDFacebookController *)sharedController;
- (void)authorize;
- (void)postMessageToWall:(NSString *)message;
#end
SDFacebookController.m
#import "SDFacebookController.h"
#import "Constants+Macros.h"
#import "SDOperationStack.h"
#interface SDFacebookController ()
#property (nonatomic, retain) SDOperationStack *operationStack;
- (void)performAuthorization;
- (void)performPostMessageToWall:(NSString *)message;
- (void)runOperation;
#end
#implementation SDFacebookController
#synthesize facebook, operationStack;
#pragma mark - Instance methods
- (void)authorize
{
NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(performAuthorization) object:nil] autorelease];
[operationStack push:operation];
[self runOperation];
}
- (void)postMessageToWall:(NSString *)message
{
NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(performPostMessageToWall:) object:message] autorelease];
[operationStack push:operation];
if (![facebook isSessionValid])
{
NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(performAuthorization) object:nil] autorelease];
[operationStack push:operation];
}
[self runOperation];
}
#pragma mark - Private methods
- (void)runOperation
{
NSOperation *operation = [operationStack pop];
[[NSOperationQueue currentQueue] addOperation:operation];
}
- (void)performAuthorization
{
if (![facebook isSessionValid])
{
NSArray *permissions = [NSArray arrayWithObject:#"publish_stream"];
[facebook authorize:permissions delegate:self];
}
}
- (void)performPostMessageToWall:(NSString *)message
{
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:message, #"message", nil];
[facebook requestWithGraphPath:#"me/feed" andParams:params andHttpMethod:#"POST" andDelegate:self];
}
#pragma mark - FBRequestDelegate
/**
* Called just before the request is sent to the server.
*/
- (void)requestLoading:(FBRequest *)request
{
DLog(#"%#", request);
}
/**
* Called when the server responds and begins to send back data.
*/
- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response
{
DLog(#"%# %#", request, response);
}
/**
* Called when an error prevents the request from completing successfully.
*/
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error
{
DLog(#"%# %#", request, error);
[[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Error", nil)
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil]
autorelease] show];
[operationStack empty];
}
/**
* Called when a request returns and its response has been parsed into
* an object.
*
* The resulting object may be a dictionary, an array, a string, or a number,
* depending on thee format of the API response.
*/
- (void)request:(FBRequest *)request didLoad:(id)result
{
DLog(#"%# %#", request, result);
if ([operationStack isEmpty] == NO)
[self runOperation];
else if ([operationStack.lastOperation.invocation selector] == #selector(performPostMessageToWall:))
[[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"MessagePosted", nil)
message:NSLocalizedString(#"Successfully posted message on Facebook.", nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil]
autorelease] show];
}
/**
* Called when a request returns a response.
*
* The result object is the raw response from the server of type NSData
*/
- (void)request:(FBRequest *)request didLoadRawResponse:(NSData *)data
{
DLog(#"%# %#", request, data);
}
#pragma mark - FBSessionDelegate
/**
* Called when the user successfully logged in.
*/
- (void)fbDidLogin
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
/**
* Called when the user dismissed the dialog without logging in.
*/
- (void)fbDidNotLogin:(BOOL)cancelled
{
}
/**
* Called when the user logged out.
*/
- (void)fbDidLogout
{
}
#pragma mark - Memory management
- (id)init
{
self = [super init];
if (self)
{
facebook = [[Facebook alloc] initWithAppId:kFacebookAppIdentifier];
operationStack = [[SDOperationStack alloc] init];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"FBAccessTokenKey"] && [defaults objectForKey:#"FBExpirationDateKey"])
{
facebook.accessToken = [defaults objectForKey:#"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:#"FBExpirationDateKey"];
}
}
return self;
}
- (void)dealloc
{
[operationStack release];
[facebook release];
[super dealloc];
}
#pragma mark - Singleton
+ (SDFacebookController *)sharedController
{
static SDFacebookController *controller = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
controller = [[self alloc] init];
});
return controller;
}
#end
SDOperationStack.h
#import <Foundation/Foundation.h>
#interface SDOperationStack : NSObject
#property (nonatomic, retain) NSInvocationOperation *lastOperation;
- (void)push:(NSOperation *)operation;
- (NSOperation *)pop;
- (BOOL)isEmpty;
- (void)empty;
#end
SDOperationStack.m
#import "SDOperationStack.h"
#interface SDOperationStack ()
#property (nonatomic, retain) NSMutableArray *array;
#end
#implementation SDOperationStack
#synthesize array, lastOperation;
- (void)dealloc
{
[lastOperation release];
[array release];
[super dealloc];
}
- (id)init
{
self = [super init];
if (self)
{
array = [[NSMutableArray alloc] init];
}
return self;
}
- (void)push:(NSInvocationOperation *)operation
{
[array addObject:operation];
}
- (NSInvocationOperation *)pop
{
if ([self isEmpty])
return nil;
self.lastOperation = (NSInvocationOperation *)[array lastObject];
[array removeLastObject];
return lastOperation;
}
- (BOOL)isEmpty
{
return [array count] == 0;
}
- (void)empty
{
[array removeAllObjects];
}
#end
I have read several posts about this but I'm not able to fix the error. If someone could please help.
Here is the code I use. I have an NSMutableArray called list.
-(void) awakeFromNib
{
prefs=[[NSUserDefaults standardUserDefaults]retain];
if ([prefs arrayForKey:#"list"]) {
list=[[NSMutableArray alloc]initWithArray:[prefs objectForKey:#"list"]];
}
else {
list=[[NSMutableArray alloc]init];
}
}
-(void)saveData
{
NSLog(#"saving data!");
[prefs setObject:list forKey:#"list"];
}
- (void)dealloc {
[self saveData];
[prefs synchronize];
[prefs release];
}
You cannot store UIView instances in the user defaults, but only objects that can be serialized in a property list (see here) Also, as #Justin said, do not retain or release the defaults object.
Thank you. However, I had earlier read that you cannot save an NSMutableArray in NSUserDefaults so I attempted to convert it to NSData and then use it.
Here's my ViewController.m file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self=[[calViewController alloc]init];
if (self!=nil) {
self.list=[aDecoder decodeObjectForKey:#"list"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:list forKey:#"list"];
}
And here's my AppDelegate.m file:
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
NSLog(#"Application did finish launching");
defaults=[NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray=[defaults objectForKey:#"lastArray"];
if (dataRepresentingSavedArray!=nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
{
listAr = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
{
listAr = [[NSMutableArray alloc] init];
NSLog(#"listAr: %#",listAr);
}
}
}
-(void) applicationWillTerminate:(UIApplication *)application
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:listAr] forKey:#"lastArray"];
}
I've never used UserDefaults before and I absolutely confused. I've read all the docs but I don't seem to be getting it right!
EDIT:
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
NSLog(#"Application did finish launching");
defaults=[NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"lastArray"]) {
NSData *dataRepresentingSavedArray=[defaults objectForKey:#"lastArray"];
if (dataRepresentingSavedArray!=nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
listAr = [[NSMutableArray alloc] initWithArray:oldSavedArray];
}
}
else
{
listAr = [[NSMutableArray alloc] init];
NSLog(#"listAr: %#",listAr);
}
}