Last active
November 7, 2021 19:55
-
-
Save tadija/79d10786e93bd127eb79ae8772235c4d to your computer and use it in GitHub Desktop.
AEGameCenter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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