Skip to content

Commit 0f9202e

Browse files
authored
Merge pull request #9 from maxxfrazer/ios14-fixes
Fix iOS 14 compatibility, using `MultipeerHelperDelegate.checkPeerToken()`
2 parents 2b92364 + 243d6d0 commit 0f9202e

File tree

7 files changed

+136
-15
lines changed

7 files changed

+136
-15
lines changed

.github/workflows/swift-build.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ on:
1313
jobs:
1414
build:
1515
runs-on: macOS-latest
16+
strategy:
17+
matrix:
18+
SDK: [iphoneos, macosx, appletvos]
1619
steps:
1720
- uses: actions/checkout@v1
1821
- name: build
1922
run: |
2023
swift package generate-xcodeproj
21-
xcodebuild clean build -project $PROJECT -scheme $SCHEME -sdk $SDK CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO
24+
xcodebuild clean build -project $PROJECT -scheme $SCHEME -sdk $SDK CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO | xcpretty
2225
env:
2326
PROJECT: MultipeerHelper.xcodeproj
2427
SCHEME: MultipeerHelper-Package
25-
SDK: iphoneos
28+
SDK: ${{ matrix.SDK }}

.swiftlint.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
line_length:
2+
ignores_comments: true
3+
cyclomatic_complexity:
4+
ignores_case_statements: true

MultipeerHelper+Example/MultipeerHelper+Example/Info.plist

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
<true/>
2323
<key>NSCameraUsageDescription</key>
2424
<string></string>
25+
<key>NSLocalNetworkUsageDescription</key>
26+
<string>This application synchronises with other iOS devices on the same network.</string>
2527
<key>UILaunchStoryboardName</key>
2628
<string>LaunchScreen</string>
2729
<key>UIRequiredDeviceCapabilities</key>
@@ -31,6 +33,11 @@
3133
</array>
3234
<key>UIStatusBarHidden</key>
3335
<true/>
36+
<key>NSBonjourServices</key>
37+
<array>
38+
<string>_helper-test._tcp</string>
39+
<string>_helper-test._udp</string>
40+
</array>
3441
<key>UISupportedInterfaceOrientations</key>
3542
<array>
3643
<string>UIInterfaceOrientationPortrait</string>

MultipeerHelper+Example/MultipeerHelper+Example/RealityViewController.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ class RealityViewController: UIViewController, ARSessionDelegate {
4646
}
4747

4848
extension RealityViewController: MultipeerHelperDelegate {
49+
50+
func shouldSendJoinRequest(
51+
_ peer: MCPeerID,
52+
with discoveryInfo: [String: String]?
53+
) -> Bool {
54+
if RealityViewController.checkPeerToken(with: discoveryInfo) {
55+
return true
56+
}
57+
print("incompatible peer!")
58+
return false
59+
}
60+
4961
func setupMultipeer() {
5062
multipeerHelp = MultipeerHelper(
5163
serviceName: "helper-test",

README.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<img src="https://github.com/maxxfrazer/MultipeerHelper/workflows/swiftlint/badge.svg?branch=main"/>
99
</p>
1010

11-
![Path Example 1](https://github.com/maxxfrazer/MultipeerHelper/blob/main/media/MultipeerHelper-Header.png?raw=true)
11+
![MultipeerHelper Header](https://github.com/maxxfrazer/MultipeerHelper/blob/main/media/MultipeerHelper-Header.png?raw=true)
1212

1313
# MultipeerHelper
1414

@@ -33,6 +33,17 @@ self.multipeerHelp = MultipeerHelper(
3333
)
3434
```
3535

36+
Because MultipeerConnectivity looks over your local network to find other devices to connect with, there are a few new things to include since iOS 14.
37+
38+
The first thing, is to include the key `NSLocalNetworkUsageDescription` in your app's Info.plist, along with a short text explaining why you need to use the local network. For example "This application needs access to the local network to find opponents.".
39+
40+
As well as the above, you also need to add another key, `NSBonjourServices`. Bonjour services is an array of Bonjour service types.
41+
For example, if your serviceName is "helper-test", you will need to add `_helper-test._tcp` and `_helper-test._udp`.
42+
43+
The two above keys are included in [the Example Project](MultipeerHelper+Example).
44+
45+
### RealityKit
46+
3647
To extend this to RealityKit's synchronization service, simply add the following:
3748

3849
```swift
@@ -41,6 +52,24 @@ self.arView.scene.synchronizationService = self.multipeerHelp.syncService
4152

4253
And also make sure that your ARConfiguration's isCollaborationEnabled property is set to true.
4354

55+
To make sure RealityKit's synchronizationService runs properly, you must ensure that the RealityKit version installed on any two devices are compatible.
56+
57+
By default, any OS using MultipeerHelper that can install RealityKit (iOS, iPadOS and macOS) will have a key added to the discoveryInfo.
58+
To use this easily, you can add the `shouldSendJoinRequest` method to your `MultipeerHelperDelegate`, and make use of the `checkPeerToken` which is accessible to any class which inherits the `MultipeerHelperDelegate`. Here's an example:
59+
60+
```swift
61+
extension RealityViewController: MultipeerHelperDelegate {
62+
func shouldSendJoinRequest(
63+
_ peer: MCPeerID,
64+
with discoveryInfo: [String: String]?
65+
) -> Bool {
66+
self.checkPeerToken(with: discoveryInfo)
67+
}
68+
}
69+
```
70+
71+
This method is used in [the Example Project](MultipeerHelper+Example).
72+
4473
### Initializer Parameters
4574

4675
#### serviceName
@@ -51,8 +80,14 @@ This is the type of service to advertise or search for. Due to how MultipeerConn
5180
- Must not begin or end with a hyphen
5281
- Must not contain hyphens adjacent to other hyphens.
5382

54-
#### sessionType (default: .both)
83+
#### sessionType (default: `.both`)
5584
This lets the service know if it should be acting as a service `host` (advertiser), `peer` (browser), or in a scenario where it doesn't matter, `both`. The default for this parameter is `both`, which is the scenario where all devices want to just connect to each other with no questions asked.
5685

57-
#### delegate (default: nil)
86+
#### peerName (default: `UIDevice.current.name`)
87+
String name of your device on the network.
88+
89+
#### encryptionPreference (default: `.required`)
90+
encryptionPreference is how data sent over the network are encrypted.
91+
92+
#### delegate (default: `nil`)
5893
This delegate object will inherit the `MultipeerHelperDelegate` protocol, which can be used for all the handling of transferring data round the network and seeing when others join and leave.

Sources/MultipeerHelper/MultipeerHelper.swift

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77

88
import MultipeerConnectivity
9-
#if !os(tvOS)
9+
#if canImport(RealityKit)
1010
import RealityKit
1111
#endif
1212

@@ -15,6 +15,7 @@ public class MultipeerHelper: NSObject {
1515
///
1616
/// `both` creates a session where all users are equal
1717
/// Otherwise if you want one specific user to be the host, choose `host` and `peer`
18+
/// A `host` will create a service advertiser, and a `peer` will create a service browser.
1819
public enum SessionType: Int {
1920
case host = 1
2021
case peer = 2
@@ -25,8 +26,7 @@ public class MultipeerHelper: NSObject {
2526
public let sessionType: SessionType
2627
public let serviceName: String
2728

28-
29-
#if !os(tvOS)
29+
#if canImport(RealityKit)
3030
/// Used for RealityKit, set this as your scene's synchronizationService
3131
@available(iOS 13.0, macOS 10.15, *)
3232
public var syncService: MultipeerConnectivityService? {
@@ -44,7 +44,6 @@ public class MultipeerHelper: NSObject {
4444
public private(set) var serviceAdvertiser: MCNearbyServiceAdvertiser?
4545
public private(set) var serviceBrowser: MCNearbyServiceBrowser?
4646

47-
4847
public weak var delegate: MultipeerHelperDelegate?
4948
/// Initializes a Multipeer Helper.
5049
/// Pass your own peerName to avoid exceptions, as there are restrictions set by Apple on what is allowed.
@@ -89,7 +88,23 @@ public class MultipeerHelper: NSObject {
8988
session.delegate = self
9089

9190
if (self.sessionType.rawValue & SessionType.host.rawValue) != 0 {
92-
serviceAdvertiser = MCNearbyServiceAdvertiser(peer: myPeerID, discoveryInfo: nil, serviceType: self.serviceName)
91+
var discoveryInfo = self.delegate?.setDiscoveryInfo?()
92+
?? [String: String]()
93+
94+
#if canImport(RealityKit)
95+
if #available(iOS 13.4, macOS 10.15.4, *) {
96+
let networkLoc = NetworkCompatibilityToken.local
97+
let jsonData = try? JSONEncoder().encode(networkLoc)
98+
if let encodedToken = String(data: jsonData!, encoding: .utf8) {
99+
discoveryInfo["compatibility_token"] = encodedToken
100+
}
101+
}
102+
#endif
103+
serviceAdvertiser = MCNearbyServiceAdvertiser(
104+
peer: myPeerID,
105+
discoveryInfo: discoveryInfo,
106+
serviceType: self.serviceName
107+
)
93108
serviceAdvertiser?.delegate = self
94109
serviceAdvertiser?.startAdvertisingPeer()
95110
}
@@ -158,6 +173,16 @@ public class MultipeerHelper: NSObject {
158173
}
159174
return connectedPeers.first { $0.displayName == name }
160175
}
176+
177+
/// Method used for disconnecting all services. Once completed,
178+
/// create a new MultipeerHelper if you want to connect to sessions again.
179+
func disconnectAll() {
180+
self.serviceAdvertiser?.stopAdvertisingPeer()
181+
self.serviceBrowser?.stopBrowsingForPeers()
182+
self.serviceAdvertiser = nil
183+
self.serviceBrowser = nil
184+
self.session.disconnect()
185+
}
161186
}
162187

163188
extension MultipeerHelper: MCSessionDelegate {
@@ -170,6 +195,7 @@ extension MultipeerHelper: MCSessionDelegate {
170195
if state == .connected {
171196
peerIDLookup[peerID.displayName] = peerID
172197
delegate?.peerJoined?(peerID)
198+
self.serviceBrowser?.stopBrowsingForPeers()
173199
} else if state == .notConnected {
174200
peerIDLookup.removeValue(forKey: peerID.displayName)
175201
delegate?.peerLeft?(peerID)
@@ -229,11 +255,10 @@ extension MultipeerHelper: MCNearbyServiceBrowserDelegate {
229255
public func browser(
230256
_ browser: MCNearbyServiceBrowser,
231257
foundPeer peerID: MCPeerID,
232-
withDiscoveryInfo _: [String: String]?
258+
withDiscoveryInfo info: [String: String]?
233259
) {
234260
// Ask the handler whether we should invite this peer or not
235-
if delegate?.shouldSendJoinRequest == nil || (delegate?.shouldSendJoinRequest?(peerID) ?? false) {
236-
print("BrowserDelegate \(peerID)")
261+
if delegate?.shouldSendJoinRequest == nil || (delegate?.shouldSendJoinRequest?(peerID, with: info) ?? false) {
237262
browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
238263
}
239264
}
@@ -246,7 +271,7 @@ extension MultipeerHelper: MCNearbyServiceBrowserDelegate {
246271
extension MultipeerHelper: MCNearbyServiceAdvertiserDelegate {
247272
/// - Tag: AcceptInvite
248273
public func advertiser(
249-
_: MCNearbyServiceAdvertiser,
274+
_ advo: MCNearbyServiceAdvertiser,
250275
didReceiveInvitationFromPeer peerID: MCPeerID,
251276
withContext data: Data?,
252277
invitationHandler: @escaping (Bool, MCSession?) -> Void

Sources/MultipeerHelper/MultipeerHelperDelegate.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import MultipeerConnectivity
2424

2525
/// Callback for when a new peer has been found. will default to accept all peers
2626
/// - Parameter peer: the `MCPeerID` of the peer who wants to join the network
27+
/// - Parameter discoveryInfo: The info dictionary advertised by the discovered peer. For more information on the contents of this dictionary, see the documentation for
28+
/// [init(peer:discoveryInfo:serviceType:)](apple-reference-documentation://ls%2Fdocumentation%2Fmultipeerconnectivity%2Fmcnearbyserviceadvertiser%2F1407102-init) in [MCNearbyServiceAdvertiser](apple-reference-documentation://ls%2Fdocumentation%2Fmultipeerconnectivity%2Fmcnearbyserviceadvertiser).
2729
/// - Returns: Bool if the peer request to join the network or not
28-
@objc optional func shouldSendJoinRequest(_ peer: MCPeerID) -> Bool
30+
@objc optional func shouldSendJoinRequest(_ peer: MCPeerID, with discoveryInfo: [String: String]?) -> Bool
2931

3032
/// Handle when a peer has requested to join the network
3133
/// - Parameters:
@@ -34,6 +36,11 @@ import MultipeerConnectivity
3436
/// - Returns: Bool if the peer's join request should be accepted
3537
@objc optional func shouldAcceptJoinRequest(peerID: MCPeerID, context: Data?) -> Bool
3638

39+
/// This will be set as the base for the discoveryInfo, which is sent out by the advertiser (host).
40+
/// The key "compatibility_token" is in use by MultipeerHelper, for checking the
41+
/// compatibility of RealityKit versions.
42+
@objc optional func setDiscoveryInfo() -> [String: String]
43+
3744
/// Peer can no longer be found on the network, and thus cannot receive data
3845
/// - Parameter peer: If a peer has left the network in a non typical way
3946
@objc optional func peerLost(_ peer: MCPeerID)
@@ -42,3 +49,31 @@ import MultipeerConnectivity
4249
@objc optional func receivedResource(_ resourceName: String, _ peer: MCPeerID, _ url: URL?, _ error: Error?)
4350
@objc optional func receivedCertificate(certificate: [Any]?, fromPeer peerID: MCPeerID) -> Bool
4451
}
52+
53+
#if canImport(RealityKit)
54+
import RealityKit
55+
extension MultipeerHelperDelegate {
56+
/// Checks whether the discovered session is using a compatible version of RealityKit
57+
/// For collaborative sessions.
58+
/// - Parameter discoveryInfo: The discoveryInfo from the advertiser
59+
/// picked up by a browser.
60+
/// - Returns: Boolean representing whether or not the two devices
61+
/// have compatible versions of RealityKit.
62+
public static func checkPeerToken(with discoveryInfo: [String: String]?) -> Bool {
63+
guard let compTokenStr = discoveryInfo?["compatibility_token"]
64+
else {
65+
return false
66+
}
67+
if #available(iOS 13.4, macOS 10.15.4, *) {
68+
if let tokenData = compTokenStr.data(using: .utf8),
69+
let compToken = try? JSONDecoder().decode(
70+
NetworkCompatibilityToken.self,
71+
from: tokenData
72+
) {
73+
return compToken.compatibilityWith(.local) == .compatible
74+
}
75+
}
76+
return false
77+
}
78+
}
79+
#endif

0 commit comments

Comments
 (0)