如何使用SwiftUI显示Game Center排行榜

12

我创建了一个测试应用程序来测试在我正在创建的一个简单的SwiftUI游戏中添加GameCenter排行榜。我一直无法弄清楚如何显示具有所有分数的Game Center排行榜。

我已经创建了一个包含所有Game Center功能(身份验证和将分数添加到排行榜)的类。这是从主要的ContentView视图中调用的。我无法弄清楚如何使它显示排行榜(即使是如果玩家尚未登录,则显示gamecenter登录屏幕也不行)。

这是我的GameCenterManager类:

class GameCenterManager {
        var gcEnabled = Bool() // Check if the user has Game Center enabled
        var gcDefaultLeaderBoard = String() // Check the default leaderboardID
        var score = 0
        let LEADERBOARD_ID = "grp.colorMatcherLeaderBoard_1" //Leaderboard ID from Itunes Connect

       // MARK: - AUTHENTICATE LOCAL PLAYER
       func authenticateLocalPlayer() {
        let localPlayer: GKLocalPlayer = GKLocalPlayer.local

           localPlayer.authenticateHandler = {(ViewController, error) -> Void in
               if((ViewController) != nil) {
                   print("User is not logged into game center")
               } else if (localPlayer.isAuthenticated) {
                   // 2. Player is already authenticated & logged in, load game center
                   self.gcEnabled = true

                   // Get the default leaderboard ID
                   localPlayer.loadDefaultLeaderboardIdentifier(completionHandler: { (leaderboardIdentifer, error) in
                    if error != nil { print(error ?? "error1")
                       } else { self.gcDefaultLeaderBoard = leaderboardIdentifer! }
                   })
                    print("Adding GameCenter user was a success")
               } else {
                   // 3. Game center is not enabled on the users device
                   self.gcEnabled = false
                   print("Local player could not be authenticated!")
                print(error ?? "error2")
               }
           }
       } //authenticateLocalPlayer()

        func submitScoreToGC(_ score: Int){
            let bestScoreInt = GKScore(leaderboardIdentifier: LEADERBOARD_ID)
            bestScoreInt.value = Int64(score)
            GKScore.report([bestScoreInt]) { (error) in
                if error != nil {
                    print(error!.localizedDescription)
                } else {
                    print("Best Score submitted to your Leaderboard!")
                }
            }
        }//submitScoreToGc()
    }

这里是ContentView结构体:

    struct ContentView: View {

        //GameCenter
        init() {
            self.gameCenter = GameCenterManager()
            self.gameCenter.authenticateLocalPlayer()
        }

        @State var score = 0
        var gcEnabled = Bool() //Checks if the user had enabled GameCenter
        var gcDefaultLeaderboard = String() //Checks the default leaderboard ID
        let gameCenter: GameCenterManager

        /*End GameCenter Variables */



        var body: some View {

            HStack {
                Text("Hello, world!")
                Button(action: {
                    self.score += 1
                    print("Score increased by 10. It is now \(self.score)")
                    self.gameCenter.submitScoreToGC(self.score)

                }) {
                    Text("Increase Score")

                }
            }
        }
    }

非常感激任何帮助解决问题的支持。


我还没有深入研究这个问题,但我怀疑你需要从查看UIViewControllerRepresentable开始。苹果的教程文档在这里:https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit - bg2b
3个回答

10

我有一个解决方案。

在我的SwiftUI应用程序Sound Matcher中成功使用了Game Center。以下是代码片段。

代码并不完全遵循SwiftUI声明性哲学,但它可以完美地工作。我在SceneDelegate和ContentView中添加了代码片段,并使用了类似于Thomas为他的测试应用程序创建的GameKitHelper类。我基于我在raywenderlich.com上找到的代码版本。

实际上,我尝试使用符合UIViewControllerRepresentable的结构作为第一次尝试,跟随bg2b相同的思路,但它一直抱怨游戏中心视图控制器需要以模态方式呈现。最终,我放弃了并尝试了我目前更成功的方法。

适用于SwiftUI 1.0和iOS 13。

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    let contentView = ContentView()
        .environmentObject(GameKitHelper.sharedInstance) // publish enabled state

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 
            options connectionOptions: UIScene.ConnectionOptions) {
    
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)

        window.rootViewController = UIHostingController(rootView: contentView)
        // new code to create listeners for the messages
        // you will be sending later
        PopupControllerMessage.PresentAuthentication
             .addHandlerForNotification(
                 self, 
                 handler: #selector(SceneDelegate
                     .showAuthenticationViewController))
                
        PopupControllerMessage.GameCenter
            .addHandlerForNotification(
                self, 
                handler: #selector(SceneDelegate
                   .showGameCenterViewController))

        // now we are back to the standard template
        // generated when your project was created
        self.window = window
        window.makeKeyAndVisible()
    }
}
// pop's up the leaderboard and achievement screen
@objc func showGameCenterViewController() {
         if let gameCenterViewController =
             GameKitHelper.sharedInstance.gameCenterViewController {
                    self.window?.rootViewController?.present(
                         gameCenterViewController,
                         animated: true,
                         completion: nil)
         }
  
}
// pop's up the authentication screen
@objc func showAuthenticationViewController() {
    if let authenticationViewController =
        GameKitHelper.sharedInstance.authenticationViewController {
              
           self.window?.rootViewController?.present(
                authenticationViewController, animated: true)
                { GameKitHelper.sharedInstance.enabled  = 
                  GameKitHelper.sharedInstance.gameCenterEnabled }
    }
  }
}

// content you want your app to display goes here
struct ContentView: View {
  

@EnvironmentObject var gameCenter : GameKitHelper
@State private var isShowingGameCenter = false { didSet { 
                        PopupControllerMessage
                           .GameCenter
                           .postNotification() }}
     
var body: some View { 
    VStack {
        if self.gameCenter.enabled
             { 
            Button(action:{ self.isShowingGameCenter.toggle()})
                { Text(
                  "Press to show leaderboards and achievements")}
             } 
        // The authentication popup will appear when you first enter
        // the view            
        }.onAppear() {GameKitHelper.sharedInstance
                               .authenticateLocalPlayer()}
    }
}

import GameKit
import UIKit
 
// Messages sent using the Notification Center to trigger 
// Game Center's Popup screen
public enum PopupControllerMessage : String
{
 case PresentAuthentication = "PresentAuthenticationViewController"
 case GameCenter = "GameCenterViewController"
}

extension PopupControllerMessage
{
  public func postNotification() {
     NotificationCenter.default.post(
        name: Notification.Name(rawValue: self.rawValue),
        object: self)
  }
    
  public func addHandlerForNotification(_ observer: Any, 
                                        handler: Selector) {
     NotificationCenter.default .
          addObserver(observer, selector: handler, name:
            NSNotification.Name(rawValue: self.rawValue), object: nil)
  }
    
}

// based on code from raywenderlich.com
// helper class to make interacting with the Game Center easier

open class GameKitHelper: NSObject,  ObservableObject,  GKGameCenterControllerDelegate  {
    public var authenticationViewController: UIViewController?
    public var lastError: Error?


private static let _singleton = GameKitHelper()
public class var sharedInstance: GameKitHelper {
    return GameKitHelper._singleton
}

private override init() {
    super.init()
}
@Published public var enabled :Bool = false
   
public var  gameCenterEnabled : Bool { 
                     return GKLocalPlayer.local.isAuthenticated }

    public func authenticateLocalPlayer () {
        let localPlayer = GKLocalPlayer.local
        localPlayer.authenticateHandler = {(viewController, error) in
           
            self.lastError = error as NSError?
             self.enabled = GKLocalPlayer.local.isAuthenticated
            if viewController != nil {
                self.authenticationViewController = viewController                  
                PopupControllerMessage
                   .PresentAuthentication
                   .postNotification()
            }
        }
    }
   
    public var gameCenterViewController : GKGameCenterViewController? { get {
         
         guard gameCenterEnabled else {  
                  print("Local player is not authenticated")
                  return nil }
        
         let gameCenterViewController = GKGameCenterViewController()
         
         gameCenterViewController.gameCenterDelegate = self
         
         gameCenterViewController.viewState = .achievements
         
         return gameCenterViewController
        }}
    
    open func gameCenterViewControllerDidFinish(_ 
                gameCenterViewController: GKGameCenterViewController) {
   
        gameCenterViewController.dismiss(
                      animated: true, completion: nil)
    }
   
}

更新:对于SwiftUI 2.0和iOS 14,代码更加简单了。
import GameKit

enum Authenticate
{
   static func user() {
       let localPlayer = GKLocalPlayer.local
       localPlayer.authenticateHandler = { _, error in
           guard error == nil else {
               print(error?.localizedDescription ?? "")
               return
           }
           GKAccessPoint.shared.location = .topLeading
           GKAccessPoint.shared.isActive =      
                            localPlayer.isAuthenticated
       }
   }
}

import SwiftUI

// content you want your app to display goes here
struct ContentView: View { 
     
var body: some View { 
      Text( "Start Game") 
        // The authentication popup will appear when you first enter
        // the view            
        }.onAppear() { Authenticate.user()}
    }
}

感谢您的帮助。我尝试了您的方法,但是我无论如何都无法在ContentView中显示按钮。我初始化了gameKitHelper并确保它已经验证了玩家,但它仍然不会显示。您有任何想法吗? - Thomas Braun
我更新了代码,通过@EnvironmentObject发布启用状态,否则按钮直到视图刷新才会出现,并且我删除了可能导致这种情况的任何内容,以简化代码。抱歉我的错误。 - Geoff Burns
现在已经发布了启用状态。视图出现后不久,授权对话框应该弹出,登录后会弹出一个欢迎回来的提示。或者,如果您已经登录,您应该立即收到欢迎回来的提示。如果没有,请转到“设置”并检查是否已启用游戏中心。如果这让您感到困惑,很抱歉,但读者的技能水平会有所不同。 - Geoff Burns
欢迎回来通知后,按钮应该立即显示。 - Geoff Burns

5

2023年更新:如评论中所述,GKScore现已弃用。我没有可供呈现的更新解决方案。

以下是部分答案。如果设备(或模拟器)已登录iCloud并在设置中启用了GameCenter,则我可以下载排行榜分数并在SwiftUI列表中显示它们。我尚未尝试使GameCenter身份验证视图控制器出现,如果不是这种情况。

感谢您在问题中提供的代码。我使用了您的GameCenterManager(),但将其放在了我的AppDelegate中:

let gameCenter = GameCenterManager()

以下是我的 ShowRankings.swift SwiftUI 视图。我能够成功地进行身份验证并获得分数。但我仍然有“异常情况”。第一次运行此代码(在模拟器中),我收到了预期的“用户未登录游戏中心”错误提示,这表明 GameCenterManager 中的 ViewController 不为空(我甚至没有尝试显示它)。但是,接下来我可以成功地获取分数并将其显示在列表中。
import SwiftUI
import GameKit

struct ShowRankings: View {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    let leaderBoard = GKLeaderboard()
    @State var scores: [GKScore] = []
    var body: some View {
        VStack {
            Button(action: {
                self.updateLeader()
            }) {
                Text("Refresh leaderboard")
            }
            List(scores, id: \.self) { score in
                Text("\(score.player.alias) \(score.value)")
            }
        }.onAppear() {
            self.appDelegate.gameCenter.authenticateLocalPlayer()
            self.updateLeader()
        }
    }
    func updateLeader() {
        let leaderBoard: GKLeaderboard = GKLeaderboard()
        leaderBoard.identifier = "YOUR_LEADERBOARD_ID_HERE"
        leaderBoard.timeScope = .allTime
        leaderBoard.loadScores { (scores, error) in
            if let error = error {
                debugPrint("leaderboard loadScores error \(error)")
            } else {
                guard let scores = scores else { return }
                self.scores = scores
            }
        }
    }
}

1
'GKScore'在iOS 14.0中已被弃用:已被GKLeaderboardScore替代。 - Peter Kreinz

0
一个替代方案是创建一个UIViewControllerRepresentable,用于GameCenter,并以排行榜ID为参数打开。这将使打开特定的排行榜变得简单。
public struct GameCenterView: UIViewControllerRepresentable {
    let viewController: GKGameCenterViewController
    
    public init(leaderboardID : String?) {
        
        if leaderboardID != nil {
            self.viewController = GKGameCenterViewController(leaderboardID: leaderboardID!, playerScope: GKLeaderboard.PlayerScope.global, timeScope: GKLeaderboard.TimeScope.allTime)
        }
        else{
            self.viewController = GKGameCenterViewController(state: GKGameCenterViewControllerState.leaderboards)
        }
        
    }
    
    public func makeUIViewController(context: Context) -> GKGameCenterViewController {
        let gkVC = viewController
        gkVC.gameCenterDelegate = context.coordinator
        return gkVC
    }
    
    public func updateUIViewController(_ uiViewController: GKGameCenterViewController, context: Context) {
        return
    }
    
    public func makeCoordinator() -> GKCoordinator {
        return GKCoordinator(self)
    }
}

public class GKCoordinator: NSObject, GKGameCenterControllerDelegate {
    var view: GameCenterView
    
    init(_ gkView: GameCenterView) {
        self.view = gkView
    }
    
    public func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
        gameCenterViewController.dismiss(animated: true, completion: nil)
    }
}

只需要在需要显示排行榜的任何位置添加以下内容即可使用。

GameCenterView(leaderboardID: "leaderBoardID")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接