Getting Started with Multipeer Connectivity
In this tutorial, you’ll learn how to transfer data between devices with no external network. You’ll also try your hand at creating a chat feature. By Andy Pereira.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started with Multipeer Connectivity
30 mins
Sending Data Between Devices
To start sending jobs between devices, first add the following method to JobConnectionManager.swift:
private func send(_ job: JobModel, to peer: MCPeerID) {
do {
let data = try JSONEncoder().encode(job)
try session.send(data, toPeers: [peer], with: .reliable)
} catch {
print(error.localizedDescription)
}
}
Here, you’re encoding the job you want to send. Then, you’re using MCSession
‘s send(_:toPeers:with:)
. This method takes three items:
- A
Data
object you want to send. In this case, it’s the serialized job. - An array of peers you want to send the data to. Since you’re only sending the job to a selected device, only one peer is in the array.
- A mode in which to send the data. The use of
reliable
here isn’t necessary, as you’re only sending one job at a time. This becomes more important if you’re trying to send multiple items and want to guarantee order.
Next, add the following block of code to the end of JobConnectionManager.swift:
extension JobConnectionManager: MCSessionDelegate {
func session(
_ session: MCSession,
peer peerID: MCPeerID,
didChange state: MCSessionState
) {
switch state {
case .connected:
print("Connected")
case .notConnected:
print("Not connected: \(peerID.displayName)")
case .connecting:
print("Connecting to: \(peerID.displayName)")
@unknown default:
print("Unknown state: \(state)")
}
}
func session(
_ session: MCSession,
didReceive data: Data,
fromPeer peerID: MCPeerID
) {
}
func session(
_ session: MCSession,
didReceive stream: InputStream,
withName streamName: String,
fromPeer peerID: MCPeerID
) {}
func session(
_ session: MCSession,
didStartReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
with progress: Progress
) {}
func session(
_ session: MCSession,
didFinishReceivingResourceWithName resourceName: String,
fromPeer peerID: MCPeerID,
at localURL: URL?,
withError error: Error?
) {}
}
There seems to be a lot going on here, but it’s actually quite simple. To send and receive data, you need to conform to MCSessionDelegate
. These methods are all required, but this part of the tutorial will only focus on two of them.
Look at session(_:peer:didChange:)
. This method is called whenever the connection state to another peer changes. When sending a job, one device asks to connect to another. If it grants permission and makes a connection, this method will be called with the state
being connected
.
In session(_:peer:didChange:)
, replace the following print statement:
case .connected:
print("Connected")
In its place, use:
case .connected:
guard let jobToSend = jobToSend else { return }
send(jobToSend, to: peerID)
Now, once the devices establish a connection, the device attempting to send the job will call send(_:to:)
, which you added above.
To handle receiving the job, in JobConnectionManager.swift, replace session(_:didReceive:fromPeer:)
with the following:
func session(
_ session: MCSession,
didReceive data: Data,
fromPeer peerID: MCPeerID
) {
guard let job = try? JSONDecoder()
.decode(JobModel.self, from: data) else { return }
DispatchQueue.main.async {
self.jobReceivedHandler?(job)
}
}
When a job is sent to an advertising device, it passes the data to this method. Here, decode the data into a JobModel
. jobReceivedHandler
is called on the main thread, as it’ll need to update the UI.
Now add the following to the end of the initializer for JobConnectionManager
:
session.delegate = self
This final step ensures that connecting to a device will call your code to send a job. It also handles decoding and adding a job to the advertising device.
Build and run. Follow the same steps of adding a job on one device and turning on RECEIVE JOBS on the other. On the first device, select the peer found in the job view. On the second device, accept the invitation to receive a job. You’ll now see the same job on both devices:
Browsing for Sessions
The last part of this tutorial will focus on a few different items as you build out the chat functionality of the app. If you open ChatConnectionManger.swift, you’ll see there’s a lot of code that looks similar to the code in JobConnectionManager.swift.
Build and run, and select the Messages tab. You’ll see the following:
The state of Messages starts where you left off from the first part of this tutorial. If you select Host a Chat Session, it’ll start advertising your device using MCNearbyServiceAdvertiser
. However, Messages will use a different service type: jobmanager-chat.
Since hosting a session is complete, you’ll need a way to join a session. When working with jobs, you wrote custom code to find sessions using MCNearbyServiceBrowser
. For this part, you’ll use Apple’s default UI for browsing for sessions.
First, in ChatConnectionManager.swift, replace join()
with the following:
func join() {
// 1
peers.removeAll()
messages.removeAll()
session = MCSession(
peer: myPeerId,
securityIdentity: nil,
encryptionPreference: .required)
session?.delegate = self
guard
let window = UIApplication.shared.windows.first,
let session = session
else { return }
// 2
let mcBrowserViewController = MCBrowserViewController(
serviceType: ChatConnectionManager.service,
session: session)
window.rootViewController?.present(mcBrowserViewController, animated: true)
}
Here’s what the code above does:
- This first part cleans up any messages or peers and creates a new session for you.
- By using
MCBrowserViewController
, you access a built-in UI for displaying peers that are advertising a service.
Next, add the following to the end of ChatConnectionManager.swift:
extension ChatConnectionManager: MCBrowserViewControllerDelegate {
func browserViewControllerDidFinish(
_ browserViewController: MCBrowserViewController
) {
browserViewController.dismiss(animated: true) {
self.connectedToChat = true
}
}
func browserViewControllerWasCancelled(
_ browserViewController: MCBrowserViewController
) {
session?.disconnect()
browserViewController.dismiss(animated: true)
}
}
This code will handle the two scenarios that can occur when the MCBrowserViewController
is presented:
- A user has selected a session to join.
- A user has canceled browsing.
Next, add the following line of code to the end of join()
:
mcBrowserViewController.delegate = self
This will ensure your code from the previous step executes.
Finally, in ChatConnectionManager.swift, replace advertiser(_:didReceiveInvitationFromPeer:withContext:invitationHandler:)
with the code below:
func advertiser(
_ advertiser: MCNearbyServiceAdvertiser,
didReceiveInvitationFromPeer peerID: MCPeerID,
withContext context: Data?,
invitationHandler: @escaping (Bool, MCSession?) -> Void
) {
invitationHandler(true, session)
}
When dealing with sending jobs, you used this method to present an alert to ask the user for permission to accept a job. Here, you’re allowing a connection to the hosting session by default.
Build and run on both devices, and go to Messages. From one device, host a session. On the second device, select Join a Chat Session. You’ll see the following:
You can select a peer in the browser view controller. Once you see Connected, select Done. You’ll then see the following: