Skip to content

Instantly share code, notes, and snippets.

@tadija
Last active November 7, 2021 19:55
Show Gist options
  • Save tadija/79d10786e93bd127eb79ae8772235c4d to your computer and use it in GitHub Desktop.
Save tadija/79d10786e93bd127eb79ae8772235c4d to your computer and use it in GitHub Desktop.
AEGameCenter
/**
* https://gist.github.com/tadija/79d10786e93bd127eb79ae8772235c4d
* Copyright © 2021 Marko Tadić
* Licensed under the MIT license
*/
import GameKit
import Combine
open class AEGameCenter: NSObject {
public var localPlayer: GKLocalPlayer {
.local
}
}
// MARK: - Authentication
extension AEGameCenter {
open func authenticate(_ handler: ((Result<Void, Error>) -> Void)?) {
localPlayer.authenticateHandler = { viewController, error in
if let vc = viewController {
self.window?.rootViewController?
.present(vc, animated: true, completion: nil)
} else if let error = error {
handler?(.failure(error))
} else {
handler?(.success(()))
}
}
}
}
// MARK: - Present UI
extension AEGameCenter: GKGameCenterControllerDelegate {
open func present(_ state: GKGameCenterViewControllerState = .default) {
let vc = GKGameCenterViewController(state: state)
vc.gameCenterDelegate = self
window?.rootViewController?
.present(vc, animated: true, completion: nil)
}
open func present(_ leaderboardID: String,
playerScope: GKLeaderboard.PlayerScope = .global,
timeScope: GKLeaderboard.TimeScope = .allTime) {
let vc = GKGameCenterViewController(
leaderboardID: leaderboardID,
playerScope: playerScope,
timeScope: timeScope
)
vc.gameCenterDelegate = self
window?.rootViewController?
.present(vc, animated: true, completion: nil)
}
open func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
window?.rootViewController?.dismiss(animated: true, completion: nil)
}
private var window: UIWindow? {
UIApplication.shared.windows
.first(where: { $0.isKeyWindow })
}
}
// MARK: - Scores
public extension GKLeaderboard {
static func load(_ leaderboardID: String) -> Future<GKLeaderboard?, Error> {
Future { promise in
GKLeaderboard.loadLeaderboards(IDs: [leaderboardID]) { (leaderboard, error) in
if let error = error {
promise(.failure(error))
} else {
promise(.success(leaderboard?.first))
}
}
}
}
static func loadScore(_ leaderboardID: String) -> AnyPublisher<Int, Never> {
GKLeaderboard
.load(leaderboardID)
.compactMap { $0 }
.flatMap { $0.localPlayerEntry() }
.compactMap({ $0?.score })
.replaceError(with: 0)
.eraseToAnyPublisher()
}
static func submitScore(_ leaderboardID: String, score: Int, context: Int = 0) -> Future<Void, Error> {
Future { promise in
GKLeaderboard.submitScore(
score, context: context, player: GKLocalPlayer.local, leaderboardIDs: [leaderboardID]
) { (error) in
if let error = error {
promise(.failure(error))
} else {
promise(.success(()))
}
}
}
}
func localPlayerEntry() -> Future<GKLeaderboard.Entry?, Error> {
Future { [weak self] promise in
self?.loadEntries(
for: .friendsOnly, timeScope: .allTime, range: NSRange(location: 1, length: 1)
) { (localPlayerEntry, _, _, error) in
if let error = error {
promise(.failure(error))
} else {
promise(.success(localPlayerEntry))
}
}
}
}
}
// MARK: - Achievements
public extension GKAchievement {
convenience init(_ identifier: String,
score: Int, goal: Int,
showBanner: Bool = false) {
self.init(identifier: identifier)
if score >= goal {
percentComplete = 100
} else {
percentComplete = round(Double(score) / Double(goal) * 100)
}
showsCompletionBanner = showBanner
}
static func loadAll() -> Future<[GKAchievement], Error> {
Future { promise in
GKAchievement.loadAchievements { (achievements, error) in
if let error = error {
promise(.failure(error))
} else {
promise(.success(achievements ?? []))
}
}
}
}
static func report(_ achievements: [GKAchievement]) -> Future<[GKAchievement], Error> {
Future { promise in
GKAchievement.report(achievements) { (error) in
if let error = error {
promise(.failure(error))
} else {
promise(.success(achievements))
}
}
}
}
static func reportOnce(_ achievements: [GKAchievement]) -> AnyPublisher<[GKAchievement], Error> {
loadAll()
.flatMap { reported in
achievements.filter { new in
if let old = reported.first(where: { $0.identifier == new.identifier }),
old.percentComplete >= new.percentComplete {
return false
} else {
return true
}
}.publisher
}
.collect()
.flatMap {
GKAchievement.report($0)
}
.eraseToAnyPublisher()
}
static func reportUpdate(_ achievements: [GKAchievement]) -> AnyPublisher<[GKAchievement], Error> {
loadAll()
.flatMap { reported in
achievements.filter { new in
if let old = reported.first(where: { $0.identifier == new.identifier }),
old.percentComplete > new.percentComplete {
return false
} else {
return true
}
}.publisher
}
.collect()
.flatMap {
GKAchievement.report($0)
}
.eraseToAnyPublisher()
}
static func resetAll() -> Future<Void, Error> {
Future { promise in
GKAchievement.resetAchievements { (error) in
if let error = error {
promise(.failure(error))
} else {
promise(.success(()))
}
}
}
}
}
// MARK: - Helpers
public enum AEGameCenterError: Error {
case leaderboardNotFound
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment