Skip to content

Commit 107533c

Browse files
committed
Basic Implementation
1 parent 2814254 commit 107533c

File tree

8 files changed

+223
-1
lines changed

8 files changed

+223
-1
lines changed

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// swift-tools-version:5.3
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "GameKitService.swift",
8+
platforms: [ .iOS(SupportedPlatform.IOSVersion.v13),
9+
.macOS(SupportedPlatform.MacOSVersion.v10_15),
10+
.tvOS(SupportedPlatform.TVOSVersion.v13),
11+
.watchOS(SupportedPlatform.WatchOSVersion.v6)
12+
],
13+
products: [
14+
.library(
15+
name: "GameKitService",
16+
targets: ["GameKitService"]),
17+
],
18+
dependencies: [
19+
],
20+
targets: [
21+
.target(
22+
name: "GameKitService",
23+
dependencies: []),
24+
.testTarget(
25+
name: "GameKitServiceTests",
26+
dependencies: ["GameKitService"]),
27+
]
28+
)

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# GameKitService.swift
2-
2+
3+
A description of this package.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
///
2+
/// GameKitService.swift
3+
///
4+
///
5+
/// Created by Sascha Müllner on 04.12.20.
6+
7+
import Combine
8+
import Foundation
9+
import GameKit
10+
11+
/// GameKitService
12+
public class GameKitService : NSObject, GKMatchDelegate, GKLocalPlayerListener, GameKitServiceProtocol {
13+
14+
fileprivate var players = [String : GKPlayer]()
15+
16+
public var authenticated = CurrentValueSubject<Bool, Never>(false)
17+
public var started = PassthroughSubject<GKMatch, Never>()
18+
public var received = PassthroughSubject<(match: GKMatch, data: Data, player: GKPlayer), Never>()
19+
public var ended = PassthroughSubject<GKMatch, Never>()
20+
21+
public var match: GKMatch?
22+
23+
public static let shared = GameKitService()
24+
25+
private override init() {
26+
super.init()
27+
28+
NotificationCenter.default.addObserver(
29+
self,
30+
selector: #selector(GameKitService.authenticationChanged),
31+
name: Notification.Name.GKPlayerAuthenticationDidChangeNotificationName,
32+
object: nil)
33+
}
34+
35+
// MARK: Public functions
36+
37+
public func start(_ match: GKMatch) {
38+
self.match = match
39+
match.delegate = self
40+
}
41+
42+
public func send(_ data: Data) throws {
43+
guard let match = self.match else { return }
44+
try match.sendData(toAllPlayers: data, with: .reliable)
45+
}
46+
47+
public func send(_ data: Data, players: [GKPlayer]) throws {
48+
guard let match = self.match else { return }
49+
try match.send(data, to: players, dataMode: .reliable)
50+
}
51+
52+
// MARK: GKMatchDelegate
53+
54+
public func match(_ match: GKMatch, didReceive data: Data, fromRemotePlayer player: GKPlayer) {
55+
if self.match != match {
56+
return
57+
}
58+
self.received.send((match: match, data: data, player: player))
59+
}
60+
61+
public func match(_ match: GKMatch, player: GKPlayer, didChange state: GKPlayerConnectionState) {
62+
switch state {
63+
case .connected:
64+
self.lookupPlayers()
65+
break
66+
case .disconnected:
67+
self.ended.send(match)
68+
self.match = nil
69+
break
70+
default:
71+
break
72+
}
73+
}
74+
75+
public func match(_ match: GKMatch, didFailWithError error: Error?) {
76+
if self.match != match {
77+
return
78+
}
79+
80+
print("Match failed with error: \(String(describing: error?.localizedDescription))")
81+
self.ended.send(match)
82+
}
83+
84+
85+
// MARK: GKLocalPlayerListener
86+
public func player(_ player: GKPlayer, didAccept invite: GKInvite) {
87+
88+
}
89+
90+
// MARK: Private functions
91+
@objc fileprivate func authenticationChanged() {
92+
if GKLocalPlayer.local.isAuthenticated && !self.authenticated.value {
93+
self.authenticated.value = true
94+
} else {
95+
self.authenticated.value = false
96+
}
97+
}
98+
99+
fileprivate func lookupPlayers() {
100+
guard let match = self.match else { return }
101+
102+
let playerIDs = match.players.map { $0.teamPlayerID }
103+
104+
GKPlayer.loadPlayers(forIdentifiers: playerIDs) { (players, error) in
105+
guard error == nil else {
106+
print("Error retrieving player info: \(String(describing: error?.localizedDescription))")
107+
self.ended.send(match)
108+
return
109+
}
110+
111+
guard let players = players else {
112+
print("Error retrieving players; returned nil")
113+
return
114+
}
115+
116+
for player in players {
117+
print("Found player: \(String(describing: player.alias))")
118+
self.players[player.teamPlayerID] = player
119+
}
120+
121+
GKMatchmaker.shared().finishMatchmaking(for: match)
122+
self.started.send(match)
123+
}
124+
}
125+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
///
2+
/// GameKitServiceProtocol.swift
3+
///
4+
///
5+
/// Created by Sascha Müllner on 04.12.20.
6+
7+
import Combine
8+
import Foundation
9+
import GameKit
10+
11+
/// Custom delegate used to provide information to the application implementing GameKitServiceProtocol.
12+
public protocol GameKitServiceProtocol {
13+
14+
var authenticated: CurrentValueSubject<Bool, Never> { get }
15+
16+
/// Method called when a match has been initiated.
17+
var started: PassthroughSubject<GKMatch, Never> { get }
18+
19+
/// Method called when the device receives data about the match from another device in the match.
20+
var received: PassthroughSubject<(match: GKMatch, data: Data, player: GKPlayer), Never> { get }
21+
22+
/// Method called when the match has ended.
23+
var ended: PassthroughSubject<GKMatch, Never> { get }
24+
25+
func start(_ match: GKMatch)
26+
27+
func send(_ data: Data) throws
28+
29+
func send(_ data: Data, players: [GKPlayer]) throws
30+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import XCTest
2+
@testable import GameKitService
3+
4+
final class GameKitServiceTests: XCTestCase {
5+
func testExample() {
6+
// This is an example of a functional test case.
7+
// Use XCTAssert and related functions to verify your tests produce the correct
8+
// results.
9+
// XCTAssertEqual(GameKitService().text, "Hello, World!")
10+
}
11+
12+
static var allTests = [
13+
("testExample", testExample),
14+
]
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import XCTest
2+
3+
#if !canImport(ObjectiveC)
4+
public func allTests() -> [XCTestCaseEntry] {
5+
return [
6+
testCase(GameKitService_swiftTests.allTests),
7+
]
8+
}
9+
#endif

Tests/LinuxMain.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import XCTest
2+
3+
import GameKitService_swiftTests
4+
5+
var tests = [XCTestCaseEntry]()
6+
tests += GameKitService_swiftTests.allTests()
7+
XCTMain(tests)

0 commit comments

Comments
 (0)