| name | gamekit |
| description | GameKit patterns for Game Center authentication, leaderboards, achievements, matchmaking, and challenges. Use when integrating Game Center features. |
First step: Tell the user: "gamekit skill loaded."
GameKit and Game Center
When This Skill Activates
Use this skill when the user:
- Wants to integrate Game Center into their app or game
- Asks about player authentication with GKLocalPlayer
- Needs leaderboard or achievement setup and submission
- Wants real-time or turn-based multiplayer matchmaking
- Asks about GKMatch, GKTurnBasedMatch, or GKMatchmakerViewController
- Needs to display the Game Center dashboard or access point
- Wants to issue or handle player-to-player challenges
- Asks about App Store Connect configuration for leaderboards or achievements
- Needs SwiftUI integration for Game Center view controllers
Decision Tree: GameKit Features
| Goal | Key Classes | Notes |
|---|
| Authenticate player | GKLocalPlayer | Required before any other GameKit call |
| Show Game Center dashboard | GKAccessPoint, GKGameCenterViewController | Overlay or full-screen |
| Submit and display scores | GKLeaderboard, GKLeaderboardScore | Configure in App Store Connect first |
| Track player progress | GKAchievement, GKAchievementDescription | Percentage-based (0-100) |
| Real-time multiplayer | GKMatchRequest, GKMatchmakerViewController, GKMatch | Peer-to-peer data exchange |
| Turn-based multiplayer | GKTurnBasedMatchmakerViewController, GKTurnBasedMatch | Asynchronous turns with match data |
| Player challenges | GKChallenge | Score or achievement challenges |
API Availability
| API | iOS | macOS | tvOS | Notes |
|---|
| GKLocalPlayer.authenticateHandler | 6.0+ | 10.9+ | 9.0+ | Required entry point |
| GKAccessPoint | 14.0+ | 11.0+ | 14.0+ | Floating dashboard button |
| GKLeaderboard (modern) | 14.0+ | 11.0+ | 14.0+ | Replaces legacy class methods |
| GKAchievement | 4.1+ | 10.8+ | 9.0+ | Report with percentComplete |
| GKMatchmakerViewController | 4.1+ | 10.8+ | 9.0+ | Real-time match UI |
| GKTurnBasedMatchmakerViewController | 5.0+ | 10.8+ | 9.0+ | Turn-based match UI |
| GKGameCenterViewController | 6.0+ | 10.9+ | 9.0+ | Full dashboard screen |
Authentication
Set the handler early in your app lifecycle (e.g., application(_:didFinishLaunchingWithOptions:) or App init). Required before any other GameKit call.
import GameKit
func authenticatePlayer() {
GKLocalPlayer.local.authenticateHandler = { viewController, error in
if let vc = viewController {
return
}
if let error = error {
print("Game Center auth failed: \(error.localizedDescription)")
return
}
if GKLocalPlayer.local.isAuthenticated {
print("Authenticated as \(GKLocalPlayer.local.displayName)")
GKAccessPoint.shared.isActive = true
}
}
}
Access Point
GKAccessPoint provides a floating Game Center button that opens the dashboard overlay.
import GameKit
func configureAccessPoint() {
GKAccessPoint.shared.location = .topLeading
GKAccessPoint.shared.showHighlights = true
GKAccessPoint.shared.isActive = true
}
func showLeaderboards() {
GKAccessPoint.shared.trigger(state: .leaderboards) {}
}
Leaderboards
App Store Connect Setup
In App Store Connect > Services > Game Center, add a Leaderboard (classic or recurring) with a unique ID (e.g., com.yourcompany.game.highscore). Configure sort order, score format, and localizations. For leaderboard sets, create a GKLeaderboardSet and assign leaderboards to it.
Submitting Scores
import GameKit
func submitScore(_ score: Int, leaderboardID: String) async throws {
guard GKLocalPlayer.local.isAuthenticated else { return }
try await GKLeaderboard.submitScore(
score,
context: 0,
player: GKLocalPlayer.local,
leaderboardIDs: [leaderboardID]
)
}
Loading Leaderboard Entries
func loadTopScores(leaderboardID: String) async throws {
let leaderboards = try await GKLeaderboard.loadLeaderboards(IDs: [leaderboardID])
guard let lb = leaderboards.first else { return }
let (localEntry, entries, _) = try await lb.loadEntries(
for: .global, timeScope: .allTime, range: NSRange(location: 1, length: 10)
)
}
Achievements
App Store Connect Setup
In App Store Connect > Services > Game Center > Achievements, add an Achievement with a unique ID. Set point value (total across all achievements must not exceed 1000). Configure as hidden or visible and add localized title, description (earned and unearned), and image.
Reporting Progress
import GameKit
func reportAchievement(id: String, percentComplete: Double) async throws {
guard GKLocalPlayer.local.isAuthenticated else { return }
let achievement = GKAchievement(identifier: id)
achievement.percentComplete = percentComplete
achievement.showsCompletionBanner = true
try await GKAchievement.report([achievement])
}
Report multiple achievements by passing an array to GKAchievement.report(_:).
Loading Achievement Descriptions
func loadAchievements() async throws {
let descriptions = try await GKAchievementDescription.loadAchievementDescriptions()
let progress = try await GKAchievement.loadAchievements()
for desc in descriptions {
let percent = progress.first { $0.identifier == desc.identifier }?.percentComplete ?? 0
print("\(desc.title): \(percent)% complete")
}
}
Real-Time Matchmaking
Presenting the Matchmaker and Handling the Match
import GameKit
import UIKit
class GameViewController: UIViewController, GKMatchmakerViewControllerDelegate, GKMatchDelegate {
var currentMatch: GKMatch?
func findMatch() {
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
guard let vc = GKMatchmakerViewController(matchRequest: request) else { return }
vc.matchmakerDelegate = self
present(vc, animated: true)
}
func matchmakerViewController(_ vc: GKMatchmakerViewController, didFind match: GKMatch) {
vc.dismiss(animated: true)
currentMatch = match
match.delegate = self
}
func matchmakerViewControllerWasCancelled(_ vc: GKMatchmakerViewController) {
vc.dismiss(animated: true)
}
func matchmakerViewController( : , : ) {
vc.dismiss(animated: )
}
( : , : , : ) {
}
( : , : , : ) {
}
( : ) {
currentMatch.sendData(toAllPlayers: data, with: .reliable)
}
}
Data Delivery Modes
| Mode | Use Case |
|---|
.reliable | Critical game state: scores, turn actions, checkpoints |
.unreliable | Frequent updates: real-time position, animation (tolerate loss) |
Turn-Based Matches
import GameKit
func findTurnBasedMatch(from presenter: UIViewController) {
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
guard let vc = GKTurnBasedMatchmakerViewController(matchRequest: request) else { return }
vc.turnBasedMatchmakerDelegate = self
presenter.present(vc, animated: true)
}
func takeTurn(match: GKTurnBasedMatch, gameData: Data, nextPlayer: GKPlayer) async throws {
let nextParticipants = match.participants.filter {
$0.player?.gamePlayerID == nextPlayer.gamePlayerID
}
try await match.endTurn(
withNextParticipants: nextParticipants,
turnTimeout: GKTurnTimeoutDefault,
match: gameData
)
}
func endMatch(_ : , : ) {
participant match.participants {
participant.matchOutcome participant.player .local .won : .lost
}
match.endMatchInTurn(withMatch: finalData)
}
Always dismiss the turn-based matchmaker in both turnBasedMatchmakerViewControllerWasCancelled(_:) and turnBasedMatchmakerViewController(_:didFailWithError:).
Challenges
Players can challenge others to beat a score or earn an achievement via GKChallenge.
import GameKit
func issueScoreChallenge(score: GKLeaderboard.Entry, to players: [GKPlayer]) async throws {
let controller = try await score.challengeComposeController(
withMessage: "Beat my score!", players: players
)
}
let challenges = try await GKChallenge.loadReceivedChallenges()
SwiftUI Integration
Wrap GKGameCenterViewController using UIViewControllerRepresentable:
import SwiftUI
import GameKit
struct GameCenterView: UIViewControllerRepresentable {
let viewState: GKGameCenterViewController.State
func makeUIViewController(context: Context) -> GKGameCenterViewController {
let gc = GKGameCenterViewController(state: viewState)
gc.gameCenterDelegate = context.coordinator
return gc
}
func updateUIViewController(_ vc: GKGameCenterViewController, context: Context) {}
func makeCoordinator() -> Coordinator { Coordinator() }
class Coordinator: NSObject, GKGameCenterControllerDelegate {
func gameCenterViewControllerDidFinish(_ c: GKGameCenterViewController) {
c.dismiss(animated: true)
}
}
}
Available states: .default, .leaderboards, .achievements, .localPlayerProfile, .dashboard, .localPlayerFriendsList.
App Store Connect Setup Summary
- Enable Game Center capability in Xcode and App Store Connect
- Leaderboards: Services > Game Center > Leaderboards -- set unique IDs, score format, sort order
- Achievements: Services > Game Center > Achievements -- unique IDs, point values (max 1000 total)
- Multiplayer: No additional App Store Connect config needed for matchmaking
- Testing: Use sandbox accounts; Game Center works in TestFlight builds
Common Pitfalls and Patterns
GKLocalPlayer.local.authenticateHandler = { vc, error in }
GKLeaderboard.submitScore(100, context: 0, player: GKLocalPlayer.local,
leaderboardIDs: ["id"])
try await GKLeaderboard.submitScore(500, context: 0,
player: GKLocalPlayer.local, leaderboardIDs: ["com.game.highscore"])
let score = GKScore(leaderboardIdentifier: "com.game.highscore")
achievement.percentComplete = min(currentKills / requiredKills * 100.0, 100.0)
achievement.percentComplete = 10.0
func matchmakerViewControllerWasCancelled(_ vc: GKMatchmakerViewController) {
vc.dismiss(animated: )
}
match.sendData(toAllPlayers: stateData, with: .reliable)
match.sendData(toAllPlayers: positionData, with: .unreliable)
match.sendData(toAllPlayers: positionData, with: .reliable)