How can I tell if the GKHostedAuthenticateViewController is gone?

I'm trying to detect when an Apple GameKit user identity is rejected and sent to a Cocos2D app. I want to show a different view right after the user is authenticated, so I am just trying to find the corresponding trigger.

Here's what I do: Following the Apple Game Center Programming Guide, I check [[GKLocalPlayer localPlayer] isAuthenticated]

, and if the user is not working, I set the AuthenticationHandler and save the returned viewController like this:

[GKLocalPlayer localPlayer].authenticateHandler = ^(UIViewController *viewController, NSError *error){
    if (viewController != nil)
    {
        //save the returned view controller to show it when user tries to access leaderboards, etc.
        _savedGCAuthViewController = viewController;
    } else if([[GKLocalPlayer localPlayer] isAuthenticated]){
        _userAuthenticated = YES;
    }
};

      

I keep the returned viewController so that it can be displayed at a more appropriate time, for example:

AppController *appDelegate = (AppController *)[[UIApplication sharedApplication] delegate];
[appDelegate.navController presentViewController:_savedGCAuthViewController animated:YES completion:nil];

      

Everything works perfectly. The problem is, I don't know when this opinion went away.

All the other questions / answers I see here on StackOverflow say that the viewController subclass in a situation like this will apply the viewDidDisappear method, but I cannot subclass the view controller because Apple does not provide access to the headers for the GKHostedAuthenticateViewController class.

I've also dug all the messages for notifications, but it doesn't look like the UIViewController is using any messages of type NSNotificationCenter.

Ideas?

I'm relatively new to iOS, but is there a way to somehow get into the view hierarchy and insert my own view that gets removed when the GKHostedAuthenticateViewController does? Maybe as a parent view controller or child?

Thank!

+3


source to share


4 answers


You can wrap GKHostedAuthenticateViewController

in your own controller before exposing it so you can access the viewDidDisappear / viewWillDisappear methods:

// Simple class to wrap GKHostedAuthenticateViewController
class AuthController: UINavigationController {

    var viewWillDisappearHandler: ((Bool) -> ())?

    convenience init(authController: UIViewController) {
        self.init(rootViewController: authController)

        self.modalInPopover = true
        self.modalPresentationStyle = UIModalPresentationStyle.FormSheet
        self.navigationBarHidden = true
        self.preferredContentSize = authController.preferredContentSize
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        viewWillDisappearHandler?(animated)
    }
}

      



Then in yours, GKLocalPlayer.authenticateHandler

you can customize your viewWillDisappearHandler:

GKLocalPlayer.localPlayer().authenticateHandler = { 
    (viewController: UIViewController?, error: NSError?) -> Void in

    if let viewController = viewController {
        let authController = AuthController(authController: viewController)
        authController.viewWillDisappearHandler = { (animated: Bool) -> () in
            println("viewController is disappearing")
        }
        self.presentViewController(authController, animated: true, completion: nil)
    } else {
        // Authenticated
    }
}

      

+2


source


For some reason, the Game Center Authentication Controller is an instance GKHostedAuthenticateViewController

, which is a private class that we are not allowed to use or reference. This does not give us any way to cleanly detect when it is rejected (unlike GKGameCenterViewController

which allows us to use the GKGameCenterControllerDelegate

.

This solution (read workaround) works by testing in the background every quarter of a second when the view controller has been fired. It's not pretty, but it works.

The code below should be part of your presentingViewController, which should be protocol compliant GKGameCenterControllerDelegate

.

Swift and Objective-C.



// Swift
func authenticateLocalUser() {
    if GKLocalPlayer.localPlayer().authenticateHandler == nil {
        GKLocalPlayer.localPlayer().authenticateHandler = { (gameCenterViewController: UIViewController?, gameCenterError: NSError?) in
            if let gameCenterError = gameCenterError {
                log.error("Game Center Error: \(gameCenterError.localizedDescription)")
            }

            if let gameCenterViewControllerToPresent = gameCenterViewController {
                self.presentGameCenterController(gameCenterViewControllerToPresent)
            }
            else if GKLocalPlayer.localPlayer().authenticated {
                // Enable GameKit features
                log.debug("Player already authenticated")
            }
            else {
                // Disable GameKit features
                log.debug("Player not authenticated")
            }
        }
    }
    else {
        log.debug("Authentication Handler already set")
    }
}

func testForGameCenterDismissal() {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC))), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
        if let presentedViewController = self.presentedViewController {
            log.debug("Still presenting game center login")
            self.testForGameCenterDismissal()
        }
        else {
            log.debug("Done presenting, clean up")
            self.gameCenterViewControllerCleanUp()
        }
    }
}

func presentGameCenterController(viewController: UIViewController) {
    var testForGameCenterDismissalInBackground = true

    if let gameCenterViewController = viewController as? GKGameCenterViewController {
        gameCenterViewController.gameCenterDelegate = self
        testForGameCenterDismissalInBackground = false
    }

    presentViewController(viewController, animated: true) { () -> Void in
        if testForGameCenterDismissalInBackground {
            self.testForGameCenterDismissal()
        }
    }
}

func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!) {
    gameCenterViewControllerCleanUp()
}

func gameCenterViewControllerCleanUp() {
    // Do whatever needs to be done here, resume game etc
}

      

Note: calls to log.error and log.debug refer to XCGLogger: https://github.com/DaveWoodCom/XCGLogger

// Objective-C
- (void)authenticateLocalUser
{
    GKLocalPlayer* localPlayer = [GKLocalPlayer localPlayer];

    __weak __typeof__(self) weakSelf = self;
    if (!localPlayer.authenticateHandler) {
        [localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError* error) {
            if (error) {
                DLog(@"Game Center Error: %@", [error localizedDescription]);
            }

            if (viewcontroller) {
                [weakSelf presentGameCenterController:viewcontroller];
            }
            else if ([[GKLocalPlayer localPlayer] isAuthenticated]) {
                // Enable GameKit features
                DLog(@"Player already authenticated");
            }
            else {
                // Disable GameKit features
                DLog(@"Player not authenticated");
            }
        })];
    }
    else {
        DLog(@"Authentication Handler already set");
    }
}

- (void)testForGameCenterDismissal
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        if (self.presentedViewController) {
            DLog(@"Still presenting game center login");
            [self testForGameCenterDismissal];
        }
        else {
            DLog(@"Done presenting, clean up");
            [self gameCenterViewControllerCleanUp];
        }
    });
}

- (void)presentGameCenterController:(UIViewController*)viewController
{
    BOOL testForGameCenterDismissalInBackground = YES;
    if ([viewController isKindOfClass:[GKGameCenterViewController class]]) {
        [(GKGameCenterViewController*)viewController setGameCenterDelegate:self];
        testForGameCenterDismissalInBackground = NO;
    }

    [self presentViewController:viewController animated:YES completion:^{
        if (testForGameCenterDismissalInBackground) {
            [self testForGameCenterDismissal];
        }
    }];
}

- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController*)gameCenterViewController
{
    [self gameCenterViewControllerCleanUp];
}

- (void)gameCenterViewControllerCleanUp
{
    // Do whatever needs to be done here, resume game etc
}

      

0


source


try KVO. Observe isBeingDismissed

propertyGKHostedAuthenticateViewController

Swift 4 introduced a Key-Path types

new way Key-Path Expression

to create them and a new observe

closure-based feature available for classes that inherit from NSObject.

//invalidate old observation before adding new
gameCenterLoginVCDismissObservation?.invalidate()

//observe when GKHostedAuthenticateViewController will be dismissed, since there no publick API to access that.
gameCenterLoginVCDismissObservation = gcLoginVC.observe(\.isBeingDismissed) { [weak self] controller, change in
    self?.gameCenterLoginVCDismissObservation?.invalidate()
    print(change)
}

      

0


source


Submit your own NSNotification

from authenticateHandler

to indicate that it's finished. The view controller must listen for this notification.

-1


source







All Articles