Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display Game Center leaderboard with SwiftUI

I created a tester app to test adding a GameCenter leaderboard to a simple SwiftUI game I am creating. I have been unable to figure out how to display the Game Center leaderboard with all the scores.

I have created a class containing all the Game Center functions (authentication and adding score to the leaderboard. This is called from the main ContentView view. I can't figure out how to make it show the leaderboard (or even the gamecenter login screen if the player isn't already logged in.)

This is my GameCenterManager class:

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()
    }

and here is the ContentView struct:

    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")

                }
            }
        }
    }

Would greatly appreciate any help in fixing the problem.

like image 914
Thomas Braun Avatar asked Nov 06 '19 02:11

Thomas Braun


People also ask

How do you make a leaderboard in Swift?

From the app home page, select the horizontal tab “Services” and the vertical tab “Game Center” to access the Game Center Settings. Under “Leaderboard”, click the + to create a new leaderboard. Select either a “Classic” or “Recurring” leaderboard and fill out the rest of the prompts.

How do I create a leaderboard in Xcode?

Navigate to the Features tab, and make sure that Game Center is selected. Then, click the plus button next to leaderboards, and choose the recurring leaderboard option. Both classic and recurring leaderboards have these six fields to configure. The leaderboard reference name is used internally in App Store Connect.

How do you authenticate Game Center?

Game Center also checks whether you configured your game for Game Center. To authenticate the user, set the authentication handler ( authenticateHandler ) on the shared instance of GKLocalPlayer that represents the player of your game as in: GKLocalPlayer. local.


1 Answers

I have a fix.

I use Game Center successfully in my SwiftUI App Sound Matcher. Code snippets to follow.

The code doesn't exactly follow the SwiftUI declarative philosophy but it works perfectly. I added snippets to SceneDelegate and ContentView plus used I used a GameKitHelper class similar to the one Thomas created for his test app. I based my version on code I found on raywenderlich.com.

I actually tried using a struct conforming to UIViewControllerRepresentable as my first attempt, following the same line of thought as bg2b, however it kept complaining that the game centre view controller needed to be presented modally. Eventually I gave up and tried my current more successful approach.

For SwiftUI 1.0 and 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)
    }
   
}

Update: For SwiftUI 2.0 and iOS 14 the code is lot easier

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()}
    }
}
like image 145
Geoff Burns Avatar answered Oct 21 '22 16:10

Geoff Burns