【SwiftUI・GameKit】GameKitを用いたLeaderboardの実装

# SwiftUIでGameCenterの実装
今回はiOSのGameKitの機能を使って、Leaderboardを使ったランキング機能の実装をSwiftUIでする方法を紹介します。
Leaderboardというのは簡単に言うとスコアボードのことで、AppStoreConnectで作成、設定できます。
アプリからこのLeaderboardにスコアを送信することでランキング化され、表示し確認することができます。

## AppStoreConnect上での設定
### Leaderboardの追加
まずはAppStoreConnectの各アプリのページのサービス欄に移動し、下記画像の＋ボタンを押します。
![スクリーンショット 2022-03-27 7.17.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357475/e7ca446c-1e26-aa17-33de-71e5cd70e4ad.png)
次に以下のフォームが表示されるので、Leaderboardのタイプを選択します。
周期Leaderboardは1日ごとなどの一定の周期で新しくデータをリセットしたい場合に利用します。
標準Leaderboardはリセットされることはなく、ずっとデータが集積してほしい場合に利用します。
今回は標準Leaderboardを利用していきます。
![スクリーンショット 2022-03-27 7.18.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357475/91a90bd9-b89e-605a-5a08-cf05bb9d98f4.png)
次にLeaderboardの設定をします。Leaderboardの参照名、LeaderboardID、その他スコアに関する設定を行います。
![スクリーンショット 2022-03-27 7.18.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357475/74e6b27a-1d98-edb0-1487-f60e86d521b9.png)
次にローカライズの設定をします。最低一つの言語設定をする必要があるので任意の言語を登録してください。
![スクリーンショット 2022-03-27 7.19.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357475/fd856ce1-524c-9d08-69b1-c8cd29b46863.png)
次にAppStoreタグに移り、以下のGameCenterのタグにチェックを入れます。
![スクリーンショット 2022-03-27 8.06.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357475/91d6400d-0195-c202-a471-2dd93a9959da.png)
次にGameCenterの欄の下にLeaderboardの欄があるので、先ほどサービス連携のタグで登録したLeaderBoardを適用します。これでAppStoreConnectでの設定は完了です。
![スクリーンショット 2022-03-27 8.06.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357475/967faa80-a9b5-1566-8e44-9fe738b95460.png)

## 認証処理
以下の処理でLeaderboardに連携するための、ユーザー認証処理ができます。
Leaderboardの表示、スコアの送信の前に認証は行っておきましょう。
```swift
func authenticateLocalPlayer() {
    let player = GKLocalPlayer.local
    player.authenticateHandler = { _, error in
        guard error == nil else {
            print(error?.localizedDescription ?? "")
            return
        }
    }
}
```
## Leaderboardの表示
### 一番簡単な実装（詳細な位置が指定できない）
まずは一番簡単にLeaderboard（スコアボード）を表示する実装を紹介します。
以下はユーザー認証後、Leaderboardを表示するためのボタンを設置する処理です。
ここで設置するボタンというのは **GKAccessPoint** のことを指します。
GKAccessPointはiOS14から新たにGameKitに追加され、isActiveをtrueにするだけで画面に描画され、Leaderboardへの遷移処理などもやってくれる優れものです。
ただ難点としては、ボタンの位置が画面の左上、左下、右上、右下の４パターンしか選べないということです。
そのため詳細な座標などを指定してLeaderboardを表示する発火ボタンを設置したい場合は別の方法をとる必要があります。
```swift
func authenticateLocalPlayer() {
    let player = GKLocalPlayer.local
    player.authenticateHandler = { _, error in
        guard error == nil else {
            print(error?.localizedDescription ?? "")
            return
        }
        GKAccessPoint.shared.isActive = localPlayer.isAuthenticated
        GKAccessPoint.shared.location = .topLeading
    }
}
```

### 詳細な位置に発火ポイントを設置する実装
詳細な位置にLeaderboardにを表示する発火ポイントを設置するには、SwiftUIで提供されているButtonなどを任意の場所に設置すればできます。そしてButtonのactionに表示フラグをtrueにする記述をすれば、.sheet(isPresented:)などでフラグtrue時に、.sheet(isPresented:)で指定したViewがモーダルとして表示されます。
これらの工程を以下のコードに記します。

まずButtonを設置し、Buttonのaction(タップ時の処理)にLeaderboard表示フラグ(isShowLeaderBoard)をtrueにする処理を書きます。
次にLeaderboard表示フラグ(isShowLeaderBoard)を引数に指定した.sheet(isPresented:)を設置し、フラグtrue時に表示するView（GameCenterViewController）を記述します。
これでButtonタップ時に、GameCenterViewControllerがモーダル表示されるコードが完成しました。
```swift
@State var isShowLeaderBoard: Bool = false

var body: some View {
    Button {
        if GKLocalPlayer.local.isAuthenticated {
            isShowLeaderBoard = true
        }
    } label: {
    }
    .sheet(isPresented: $isShowLeaderBoard) {
        GameCenterViewController()
    }
}
```
次にLeaderboardを表示するGameCenterViewControllerを記述します。
Leaderboardを表示するGKGameCenterViewControllerはUIViewControllerを継承しているので、UIViewControllerRepresentableを使用しSwiftUIでも描画可能にするために以下のように実装します。
ポイントはGKGameCenterViewControllerのデリゲートメソッドをCoordinatorに記述し、画面を閉じるコードを書くことでモーダルが閉じる実装を忘れないでください。
```swift
struct GameCenterViewController: UIViewControllerRepresentable {

    func makeUIViewController(
        context: UIViewControllerRepresentableContext<GameCenterViewController>
    ) -> GKGameCenterViewController {
        let gcvc: GKGameCenterViewController = GKGameCenterViewController()
        gcvc.gameCenterDelegate = context.coordinator
        return gcvc
    }

    func updateUIViewController(
        _ uiViewController: GKGameCenterViewController,
        context: UIViewControllerRepresentableContext<GameCenterViewController>
    ) {}

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    class Coordinator: NSObject, GKGameCenterControllerDelegate {
        func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
            // モーダルを閉じる
            gameCenterViewController.dismiss(animated: true, completion: nil)
        }

    }
}
```

## スコアの送信
Leaderboardにスコアを送信するには、GKLeaderboardのクラスメソッドであるsubmitScoreにスコアやleaderboardの諸々の引数を指定して、実行することで送信できます。
```swift
func sendLeaderboardWithID(score: Int) {
    if GKLocalPlayer.local.isAuthenticated {
        GKLeaderboard.submitScore(score, context: 0, player: GKLocalPlayer.local, leaderboardIDs: ["LeaderboardのID"]) { error in
            print(error ?? "")
        }
    } else {
        print("GameCenterにログインしていません")
    }
}
```

# 参考
https://developer.apple.com/forums/thread/651161
https://developer.apple.com/documentation/gamekit/