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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Binding the UI

Next, in JobListView.swift, add the following property:

@ObservedObject var jobConnectionManager: JobConnectionManager

Then replace the initializer with:

init(jobListStore: JobListStore = JobListStore()) {
  self.jobListStore = jobListStore
  jobConnectionManager = JobConnectionManager { job in
    jobListStore.jobs.append(job)
  }
}

In the code above, you’re doing two things:

  1. Adding an observed property to manage your advertising state.
  2. Initializing the same property. JobConnectionManager will send a job through this closure when it receives one. This code will append that received job to jobListStore, which will get used a little later on.

Finally, replace headerView in JobListView.swift with the following:

var headerView: some View {
  Toggle("Receive Jobs", isOn: $jobConnectionManager.isReceivingJobs)
}

By binding your Toggle to isReceivingJobs, your app will now turn advertising on and off.

Now remove the following property from JobListView.swift, as it’s no longer used:

@State private var isReceivingJobs = false

Build and run, and turn the RECEIVE JOBS toggle on and off. You’ll see the following in the Xcode Console:

Turning advertising toggle on and off

Debug statements showing advertising started and stopped

Discovering Devices

Now that you have the ability to advertise a device, the next step is to discover devices. Start by adding the following properties to JobConnectionManager.swift:

@Published var employees: [MCPeerID] = []
private var nearbyServiceBrowser: MCNearbyServiceBrowser

The first new property, employees, stores the devices discovered through the service you set up in the previous section.

The next property is MCNearbyServiceBrowser. This class handles all the work of discovering devices that have turned on advertising.

Next, in the initializer for JobConnectionManager, initialize nearbyServiceBrowser before the call to super:

nearbyServiceBrowser = MCNearbyServiceBrowser(
  peer: myPeerId, 
  serviceType: JobConnectionManager.service)

MCNearbyServiceBrowser looks similar to MCNearbyServiceAdvertiser. It requires a peer ID and a service. If both of these properties use JobConnectionManager.service, they’ll be able to discover and communicate with each other.

Add the following to the end of the JobConnectionManager.swift:

extension JobConnectionManager: MCNearbyServiceBrowserDelegate {
  func browser(
    _ browser: MCNearbyServiceBrowser,
    foundPeer peerID: MCPeerID,
    withDiscoveryInfo info: [String: String]?
  ) {
    // 1
    if !employees.contains(peerID) {
      employees.append(peerID)
    }
  }

  func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
    guard let index = employees.firstIndex(of: peerID) else { return }
    // 2
    employees.remove(at: index)
  }
}

Implementing MCNearbyServiceBrowserDelegate allows you to respond to events when peers or devices are discovered.

Two things are happening here:

  1. When the browser discovers a peer, it adds it to employees.
  2. Conversely, when the browser looses a peer, it removes it from the list.

Now, add the following to the end of the initializer of JobConnectionManager:

nearbyServiceBrowser.delegate = self

This ensures your code from the previous step will be called as devices are discovered or lost.

Before you can set up the UI to show devices, add the following methods to enable and disable browsing in JobConnectionManager:

func startBrowsing() {
  nearbyServiceBrowser.startBrowsingForPeers()
}

func stopBrowsing() {
  nearbyServiceBrowser.stopBrowsingForPeers()
}

By default, your service browser won’t be browsing for peers. You can control the state by starting and stopping this service.

Rendering the List

To render the list, start by adding the following property to both JobView.swift and JobListRowView.swift:

@EnvironmentObject var jobConnectionManager: JobConnectionManager

Since you want to share model data between the job views, set up your property as an EnvironmentObject.

Next, in JobListView.swift, in body, replace the Section‘s body with the following:

ForEach(jobListStore.jobs) { job in
  JobListRowView(job: job)
    .environmentObject(jobConnectionManager)
}

This is now sharing your jobConnectionManager with JobListRowView.

You’ll need to set jobConnectionManager on one more view. In JobListRowView.swift, replace the destination of the NavigationLink with the following:

JobView(job: job).environmentObject(jobConnectionManager)

Like in the last step, it’s sharing jobConnectionManager with JobView.

Again, you’re almost there!

In JobView.swift, replace EmptyView(), which you’ll find in the last Section, with the following:

ForEach(jobConnectionManager.employees, id: \.self) { employee in
  HStack {
    Text(employee.displayName)
      .font(.headline)
    Spacer()
    Image(systemName: "arrowshape.turn.up.right.fill")
  }
}

This will populate the section with every employee found while browsing. It will then add and remove them to and from this list as they’re discovered or lost.

Finally, in JobView.swift, add the following to the end of the List after setting the navigation title:

.onAppear {
  jobConnectionManager.startBrowsing()
}
.onDisappear {
  jobConnectionManager.stopBrowsing()
}

When the job view appears, it’ll start browsing for peers, and it’ll stop when the view disappears. You’re all set!

Build and run on two different devices. You’ll see the following:

Two devices running with browsing and advertising

On one device, add a job, and then select this job from the main list.

On the other device, turn on RECEIVE JOBS. You’ll now see the following:

One device browsing for peers, the other advertising on

Finally, turn off RECEIVE JOBS from the second device, which removes the peer from the list of the first device:

One devices browsing for peers, with no peers discovered

Inviting Peers

Now the bulk of the work is complete, you’re ready to start sending jobs between devices. The first step is to wire up inviting devices to connect to each other.

Open JobConnectionManager.swift and add a new property:

private var jobToSend: JobModel?

When two devices connect, it happens asynchronously and through delegate methods. This property will temporarily save the job you’re sending.

Next, add the following method to the class:

func invitePeer(_ peerID: MCPeerID, to job: JobModel) {
  // 1
  jobToSend = job
  // 2
  let context = job.name.data(using: .utf8)
  // 3
  nearbyServiceBrowser.invitePeer(
    peerID, 
    to: session,
    withContext: context,
    timeout: TimeInterval(120))
}

Here’s a breakdown of what you added, step by step:

  1. Save the job until it’s needed
  2. Create a context. This is serializing the job’s name into Data.
  3. Ask your service browser invite a peer using the other device’s peer ID, passing the serialized job name as the context

Inviting a peer from one device will prompt the other device to connect using advertiser(_:didReceiveInvitationFromPeer:withContext:invitationHandler:), which you added earlier on. The browsing device creates the context, and the advertising peer receives it to help it determine if it wants to accept the job.

In JobView.swift, add a tap gesture to the rows that display nearby devices by adding the following to the end of the HStack that makes up the row:

.onTapGesture {
  jobConnectionManager.invitePeer(employee, to: job)
}

Tapping a nearby device or peer from the list will trigger the invitation process.

Build and run. Again, create a job on one device, and turn on RECEIVE JOBS on the other. Select the job from the main list and select the nearby device. You’ll see an invitation show up on the second device, like below:

Showing an invitation prompt before sharing job