未登录 Game Center 时应用程序崩溃

App crashing when not logged into Game Center

目前,我的应用会提示用户在打开应用后立即登录 Game Center。登录后,他们可以查看他们的成就和排行榜。然而;如果用户拒绝登录游戏中心,然后按下排行榜或成就按钮,整个应用程序就会崩溃。在这种情况下,应用程序应该做的是重新提示用户登录。任何建议将不胜感激。

class viewController: UIViewController, GKGameCenterControllerDelegate {

    var highscore = NSUserDefaults.standardUserDefaults().integerForKey("highscore")
    var loggedin = 1

    override func viewDidLoad() {
        super.viewDidLoad()
        login()
    }

    func login() {
        println("Game Center Login Called")
        let localPlayer = GKLocalPlayer.localPlayer()
        loggedin = 2

        // Handle the authentication
        localPlayer.authenticateHandler = {(Home: UIViewController!, error: NSError!) -> Void in
            if Home != nil {
                println("Authentication is being processed.")
                self.presentViewController(Home, animated: true, completion: nil)

            } else {
                println("Player has been successfully authenticated.")
            }
        }
    }

    func showLeaderboard() {
        let gkScore = GKScore(leaderboardIdentifier: "high_Score_Leader_Board")
        gkScore.value = Int64(highscore)
        GKScore.reportScores([gkScore], withCompletionHandler: ( { (error: NSError!) -> Void in
            if (error != nil) {
                // handle error
                println("Error: " + error.localizedDescription);
            } else {
                println("Score reported: \(gkScore.value)")
            }
        }))

        var gcViewController: GKGameCenterViewController = GKGameCenterViewController()
        gcViewController.gameCenterDelegate = self

        gcViewController.viewState = GKGameCenterViewControllerState.Leaderboards

        gcViewController.leaderboardIdentifier = "high_Score_Leader_Board"
        self.showViewController(gcViewController, sender: self)
        self.presentViewController(gcViewController, animated: true, completion: nil)
    }

    @IBAction func gameCenterButtoPressed(sender: AnyObject) {
        if loggedin == 2 {
            showLeaderboard()

        }

        else {
            login()
        }
    }

    @IBAction func pointButtonScored(sender: AnyObject) {
       ReportAchievment("testbutton", percentComplete: 100)
    }

    func gameCenterViewControllerDidFinish(gcViewController: GKGameCenterViewController!)
    {
        self.dismissViewControllerAnimated(true, completion: nil)
    }

    func showAchievements() {
        var gcViewController: GKGameCenterViewController = GKGameCenterViewController()
        gcViewController.gameCenterDelegate = self

        gcViewController.viewState = GKGameCenterViewControllerState.Achievements

        self.showViewController(gcViewController, sender: self)
        self.presentViewController(gcViewController, animated: true, completion: nil)
    }

    func ReportAchievment(identifier : String, percentComplete : Double)
    {
        var achievement = GKAchievement(identifier: identifier)

        if(achievement != nil)
        {
            achievement.percentComplete = percentComplete;
            achievement.showsCompletionBanner = true

            GKAchievement.reportAchievements([achievement], withCompletionHandler: { (error : NSError!) -> Void in
                println("Achievement reported")
            })
        }
    }

    @IBAction func achievementButtonPressed(sender: AnyObject) {

       showAchievements()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

这是我用来验证 Game Center 玩家身份的代码。您可以在游戏加载时调用 checkIfPlayerIsLoggedIn() 函数,这将检查玩家是否已登录。如果玩家未登录,它将移至 loginPlayer() 函数,该函数会尝试如果已经设置了 Game Center,则让玩家登录,如果没有,它将显示 Game Center 登录屏幕。如果玩家选择不登录,它将 return 一个错误,您可以从那里决定如何处理它(在您的情况下,这可能会告诉用户他们必须登录并尝试显示视图控制器)

let localPlayer = GKLocalPlayer.localPlayer()
var delegate: GameCenterInteractorNotifications?
var callingViewController: UIViewController?

func checkIfPlayerIsLoggedIn()
{
    if (self.localPlayer.authenticated == false)
    {
        self.loginPlayer()
    } else
    {
        self.localPlayer.registerListener(self)
        // Do achievement loading and matchmaking handling here
    }
}

private func loginPlayer()
{
    self.delegate?.willSignIn()

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

        if (viewController != nil)
        {
            dispatch_async(dispatch_get_main_queue(), {
                self.presentViewController(viewController, animated: true, completion: nil)
            })
        }

        else if (self.localPlayer.authenticated == true) // Player signed in
        {
            self.localPlayer.registerListener(self)
            self.delegate?.didSignIn()
        }

        else // Player did not sign in
        {
            self.delegate?.failedToSignIn()
        }

        if (error != nil)
        {
            println("Failed to sign in with error:\(error.localizedDescription).")
            self.delegate?.failedToSignInWithError(error)
            // Add code to determine what to do with the error
        }
    }
}

Zachary post 提供了一个 authenticateHandler 的好例子。我将对他的 post:

进行一些额外的观察
  1. 像 Zachary 一样,始终检查传递给处理程序的 NSError。如果设置了错误,则 VC 将为零。因此,在 OP 的原始代码中,它只检查是否设置了 VC,ERROR + NULL VC 将通过一条路径,假设用户实际上没有登录。

  2. 检查NSError时,error.code ==2表示用户取消登录。在 OP 的情况下,他们可能想要专门检查 error.code == 2,然后禁用游戏中无法运行的部分(并向用户提供一些有关如何修复它的说明)。

  3. 从 IOS8.4 开始说到 "fixing it,",无法重新显示登录 VC。如果他们取消了它,或者如果游戏中心随机取消了他们的授权(发生了),用户唯一能做的就是终止游戏并完全重新启动它。这将启动一个新的登录序列。

  4. 取消沙盒登录 3 次将永久禁用沙盒访问,需要对相关设备进行完全重置。因此,计算(并保存)您看到 error.code == 2 出现的次数并在用户即将锁定他们的机器时警告用户是个不错的主意。(不再相关因为沙盒随着 IOS9)

  5. 消失了
  6. 最后,至少有一处设置错误的bug,但是localPlayer.authenticated还是会报YES。一天晚上我一直工作到凌晨 3 点,试图解决这个问题。它不一致,似乎与 error.code == -1001 更相关(在飞行模式下,或网络完全丢失时)。我在这个问题上与 Apple 有一个 bug。所以道德是:无论 localPlayer.authenticated 说什么,都要检查传递给 authenticateHandler 的 NSerror。

更新 #5:Apple 在 #5 上关闭了我的错误,说这是按预期工作的。 Apple 似乎厌恶告诉用户错误的事情……在某种程度上,当您实际上与游戏中心完全没有连接时,您将显示为 "logged in" 使用以前缓存的凭据并显示以前缓存的排行榜。在我看来,这只会让不了解如何登录但实际上无法玩游戏的用户感到沮丧和困惑。