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 4 of 4 of this article. Click here to view the first page.

Seeing Connected Peers

The chat room isn’t helpful if you can’t see who’s joined. So in session(_:peer:didChange:), find the following cases:

case .connected:
  print("Connected")
case .notConnected:
  print("Not Connected")

Then replace them with code below:

case .connected:
  if !peers.contains(peerID) {
    // 1
    DispatchQueue.main.async {
      self.peers.insert(peerID, at: 0)
    }
    // 2
    if isHosting {
      sendHistory(to: peerID)
    }
  }
case .notConnected:
  DispatchQueue.main.async {
    // 3
    if let index = self.peers.firstIndex(of: peerID) {
      self.peers.remove(at: index)
    }
    // 4
    if self.peers.isEmpty && !self.isHosting {
      self.connectedToChat = false
    }
  }

In the previous section, you used this method to know when to send jobs. Here, you’re using it to do the following:

  1. If a user connects to the chat, add them to the list of peers.
  2. If you’re the host, send the entire chat history to the newly connected user. You’ll implement this later.
  3. When a person disconnects, remove them from the list of peers.
  4. If the host and all the other peers leave, end the session automatically.

Build and run on both devices, hosting on one and joining on the other. You’ll now see both users in the top of the chat, like below:

View peers at the top of the chat

Sending Data to Multiple Devices

Now you’re ready to start sending messages between users! In ChatConnectionManager.swift, replace send(_:) with the following code:

func send(_ message: String) {
  // 1
  let chatMessage = ChatMessage(
    displayName: myPeerId.displayName, 
    body: message)
  messages.append(chatMessage)
  // 2
  guard 
    let session = session,
    let data = message.data(using: .utf8),
    !session.connectedPeers.isEmpty 
  else { return }

  do {
    // 3
    try session.send(data, toPeers: session.connectedPeers, with: .reliable)
  } catch {
    print(error.localizedDescription)
  }
}

Here’s a breakdown of the code above:

  1. Create a ChatMessage with the relevant data needed for local use.
  2. Encode the message string.
  3. Use MCSession to send to all the connected peers.

Next, replace session(_:didReceive:fromPeer:) with the following:

func session(
  _ session: MCSession,
  didReceive data: Data,
  fromPeer peerID: MCPeerID
) {
  guard let message = String(data: data, encoding: .utf8) else { return }
  let chatMessage = ChatMessage(displayName: peerID.displayName, body: message)
  DispatchQueue.main.async {
    self.messages.append(chatMessage)
  }
}

Build and run on both devices, and create a chat session between the two. You’ll see the following:

Sending messages between devices

If you leave the chat and come back, you’ll see the chat history is gone. You can remedy this by sending the entire chat history to users as they join.

Replace sendHistory(to:) with the following:

func sendHistory(to peer: MCPeerID) {
  // 1
  let tempFile = URL(fileURLWithPath: NSTemporaryDirectory())
    .appendingPathComponent("messages.data")
  guard let historyData = try? JSONEncoder().encode(messages) else { return }
  // 2
  try? historyData.write(to: tempFile)
  // 3
  session?.sendResource(
    at: tempFile,
    withName: "Chat_History",
    toPeer: peer
  ) { error in
    if let error = error {
      print(error.localizedDescription)
    }
  }
}

This code will execute on the hosting device. Here’s what happens:

  1. Create the URL in your temporary directory.
  2. Write the chat history to a file at this URL.
  3. Use MCSession to send a resource instead of Data.

Since you’re not sending data directly, you’ll need to use a different delegate method of MCSessionDelegate. Replace session(_:didFinishReceivingResourceWithName:fromPeer:at:withError:) with the following:

func session(
  _ session: MCSession,
  didFinishReceivingResourceWithName resourceName: String,
  fromPeer peerID: MCPeerID,
  at localURL: URL?,
  withError error: Error?
) {
  // 1
  guard 
    let localURL = localURL,
    let data = try? Data(contentsOf: localURL),
    // 2 
    let messages = try? JSONDecoder().decode([ChatMessage].self, from: data) 
  else { return }

  DispatchQueue.main.async {
    // 3
    self.messages.insert(contentsOf: messages, at: 0)
  }
}

This code executes on the peer connecting to the session. Here’s what’s happening:

  1. If the resource was successfully saved locally on your device, you receive the URL here.
  2. The content of the file is decoded into ChatMessage.
  3. The messages are inserted in your local messages, which display when you join the chat.

Build and run on both devices. Create a chat session and send messages between both devices, like below:

A chat session with messages sent

On the devices you used to join the session, choose Leave:

A chat session, with a user that left after messages were sent

Rejoin the same session, and you’ll see the entire chat history, as found below:

Chat session where history is sent to newly joining user.

Where to Go From Here?

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

You should now feel comfortable using Multipeer Connectivity and sending communication between devices without the internet. There’s still more to learn, such as sending and receiving streams between devices.

You can learn more by checking out more of our iOS & Swift Tutorials or by referring to Apple’s documentation.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!