6

奇妙な理由により、Facebook は、実際には単なる通知オブザーバーである完了ハンドラーを作成することにしました。彼らがこれを行っている理由を無視して、コールバック内のオブジェクトを保持できないため、FBSession を開くリクエストを作成し、コールバックを 1 回だけ発生させる何らかの方法が必要です。

ユーザーがボタンを押したときに「publish_actions」権限を持つ Facebook セッションが開いていない場合、アクティビティ インジケーターを表示して、Facebook セッションが開いている間は UI との対話を防止する必要があります。

以下は、Facebook へのログインに使用されているメソッドです。

- (void)loginToFacebookFromViewController:(UIViewController *)viewController completion:(void (^)(NSString *facebookAccountName))completion
{
    if([self isLoggedIntoFacebook] == NO)
    {
        [viewController startLoadingAnimation];

        [FBSession openActiveSessionWithPublishPermissions: @[ @"publish_actions" ] defaultAudience:FBSessionDefaultAudienceFriends allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error)
            {
                if(error == nil)
                {
                    [FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id<FBGraphUser> user, NSError *error)
                        {
                            NSString *accountName = [user name];

                            if(error == nil)
                            {
                                NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
                                [standardUserDefaults setObject:accountName forKey:TSFacebookAccountName];
                                [standardUserDefaults synchronize];
                            }

                            [viewController stopLoadingAnimation];

                            if(completion != nil)
                                completion(accountName);
                        }];
                }
                else
                {
                    [viewController stopLoadingAnimation];

                    if(completion != nil)
                        completion(nil);
                }
            }];
    }
    else
    {
        if(completion != nil)
            completion([self facebookAccountName]);
    }
}

完了ハンドラーが保持されていない場合 (セッション状態が変化したときに NSNotifications がポストされるため、これも意味をなさない理由で)、このメソッドは完全に機能します。

アプリは Facebook を中心に展開していないため、セッション状態がいつ変化するかについては関心がありません。ユーザーが何かを Facebook に共有しようとして、アクティブなセッションが開かれていない場合、ログイン プロセスが開始され、セッションが開かれている場合は共有プロセスが続行されます。そうしないと、ユーザーにエラー メッセージが表示されます。

Facebook がこの種の機能をどのように実装することを期待しているかを誰か説明してもらえますか。それはかなり一般的なユース ケースのように思われ、誰かが「デフォルトの Facebook ダイアログを使用しない理由」について言及する前に、カスタム UI をユーザーに提示する必要があるためです。 Open Graph の投稿に使用される特定の情報を収集するためのユーザー。

4

2 に答える 2

7

私のように FBSessionStateHandler からの状態変更の受信を気にしない場合は、以下のコードを使用して Facebook にログインし、保持されない完了ブロックを提供する必要があります。

メソッドに渡された完了ブロックは、状態の変化が発生したときに常に使用され、すぐに nil になるため、別の状態の変化が発生した場合は無視されます。

typedef void(^FacebookLoginCompletionBlock)(id<FBGraphUser> user);

- (void)loginToFacebookFromWithCompletionBlock:(FacebookLoginCompletionBlock)completionBlock
{
    // If the user is not logged into Facebook attempt to open a session with "publish_actions" permissions.
    if([[FBSession activeSession] isOpen] == NO)
    {
        // Copy the completion block to a local variable so it can be nil'd out after it is used to prevent retain loops.
        __block FacebookLoginCompletionBlock copiedCompletionBlock = completionBlock;

        [FBSession openActiveSessionWithPublishPermissions:@[ @"publish_actions" ] defaultAudience:FBSessionDefaultAudienceFriends allowLoginUI:YES completionHandler:^(FBSession *session, FBSessionState status, NSError *error)
            {
                // Only attempt to run any of this code if there is a completion block to call back to. If completion block is nil than it has already been used and this is a state change that we do not care about.
                if(copiedCompletionBlock != nil)
                {
                    // Because this method is only concerned with the user logging into Facebook just worry about the open state occuring with no errors.
                    if(status == FBSessionStateOpen && error == nil)
                    {
                        // If the user successfully logged into Facebook download their basic profile information so the app can save the information to display to the user what account they are logged in under.
                        [FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id<FBGraphUser> user, NSError *error)
                            {
                                if(copiedCompletionBlock != nil)
                                    copiedCompletionBlock(user);

                                // nil out the copied completion block so it is not retained and called everytime the active FBSession's state changes.
                                copiedCompletionBlock = nil;
                            }];
                    }
                    // Because this method is only concerned with the user logging into Facebook if any other state is triggered call the completion block indicating that there was a failure.
                    else
                    {
                        if(copiedCompletionBlock != nil)
                            copiedCompletionBlock(nil);

                        // nil out the copied completion block so it is not retained and called everytime the active FBSession's state changes.
                        copiedCompletionBlock = nil;
                    }
                }

                // This block will exist the lifespan of the application because for some bizarre reason Facebook retains the completion handler for their open active session methods. Throw in some code that will display an error to the user if any session state changes occur that Facebook thinks the user should be aware of. Your code should be always checking if a active Facebook session exists before taking any action so not being aware of these changes should not be any issue. Worst case scenario you can listen for FBSessionDidSetActiveSessionNotification, FBSessionDidUnsetActiveSessionNotification, FBSessionDidBecomeOpenActiveSessionNotification or FBSessionDidBecomeClosedActiveSessionNotification notifications.
                if ([error fberrorShouldNotifyUser] == YES)
                {
                    NSString *alertTitle = @"Error logging into Facebook";
                    NSString *alertMessage = [error fberrorUserMessage];

                    if ([alertMessage length] == 0)
                        alertMessage = @"Please try again later.";

                    UIAlertView *alertView =  [[UIAlertView alloc] initWithTitle:alertTitle message:alertMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];

                    [alertView show];
                }
            }];
    }
    // If the user is already logged into Facebook immediately call the completion block with the user object that should have been saved when the user previously logged in.
    else
    {
        if(completionBlock != nil)
            completionBlock([self facebookUser]);
    }
}
于 2013-05-24T19:44:38.337 に答える
1

同様の動作を実装する方法は、セッション ロジックをシングルトンに配置することです。シングルトンは、セッションが変更されるたびに FB によって安全に呼び出すことができる完了ハンドラーを実装します。次に、完了ハンドラーは、必要に応じて監視するために、他のコントローラーに独自の通知を投稿します。

FB 認証に関するいくつかのメソッドと通知を定義したMySessionシングルトンがあるとします。ビュー コントローラのロジックは次のようになります。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // observe MySession notifications
    [MySession.sharedSession addObserver:self];
}

- (void)dealloc
{
    // stop observing MySession notifications
    [MySession.sharedSession removeObserver:self];
}

// this is a user action that requires FB auth
- (void)someActionRequiringFacebookAuth
{
    if (![MySession.sharedSession isLoggedIntoFacebook]) {
        self.postAuthAction = @selector(someActionRequiringFacebookAuth);
        [MySession.sharedSession loginToFacebook];
    }

    else {
        // perform action
    }
}

// notification posted by MySession before a FB auth attempt
- (void)mySessionWillAttemptFacebookAuth:(NSNotification *)notification
{
    [self startLoadingAnimation];
}

// notification posted by MySession if a FB auth attempt succeeds
- (void)mySessionDidAuthWithFacebook:(NSNotification *)notification
{
    [self stopLoadingAnimation];
    if (self.postAuthAction) {
        [self performSelector:self.postAuthAction];
        self.postAuthAction = nil;
    }
}

// notification posted by MySession if a FB auth attempt fails
- (void)mySessionFacebookAuthDidFail:(NSNotification *)notification
{
    [self stopLoadingAnimation];
    // display error if desired
    self.postAuthAction = nil;
}
于 2013-04-17T02:14:38.853 に答える