Multipeer Connectivity on iOS 8 with Swift

Discover, connect, and transmit data between devices.

portable_handsetMultipeer Connectivity is a means of identifying and connecting services together, and allows for very easy passing back and forth of data with minimal setup. If you are thinking that this seems similar to Bonjour, you are correct; however, Bonjour is for service discovery, not connection or transmission. Multipeer Connectivity handles everything in one neat package.

Multipeer Connectivity is comprised of a few different components, but it works by having applications advertise their services asking if anyone is available to connect. The service browser listens for these advertisements and can request to create a connection between the devices. If the connection is accepted, a session is created with every one inside the session being represented by a peer object.

To demonstrate how this works, we’ll make a simple chat application using Swift, Apple’s new language.

Note that this exercise is written for iOS 8, but the Multipeer Connectivity framework also works on OS X.

  1. Create a new Single View iPhone application called MultipeerChat.
  2. Open the project in the project navigator and the add the MultipeerConnectivity framework into the project from the Linked Frameworks and Libraries section.
  3. Open the Main.storyboard, and add a UITextView. Make it take up most of the space, and set its editable field to be false in the Attributes Inspector.
  4. Add a UITextField and add two buttons. Label one Browse and the other Send.
  5. Connect the UITextView up to a property called chatView.
  6. Connect the UITextField up to a property called messageField.
  7. Connect the browse button up to an action called showBrowser, and the Send button up to an action called sendChat.

When complete, your interface should look like the following image.

multipeer-ui

Modify the ViewController so that is looks like the following:

import UIKit
import MultipeerConnectivity

class ViewController: UIViewController, MCBrowserViewControllerDelegate,
    MCSessionDelegate {
    
    let serviceType = "LCOC-Chat"
    
    var browser : MCBrowserViewController!
    var assistant : MCAdvertiserAssistant!
    var session : MCSession!
    var peerID: MCPeerID!
                            
    @IBOutlet var chatView: UITextView!
    @IBOutlet var messageField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.peerID = MCPeerID(displayName: UIDevice.currentDevice().name)
        self.session = MCSession(peer: peerID)
        self.session.delegate = self
        
        // create the browser viewcontroller with a unique service name
        self.browser = MCBrowserViewController(serviceType:serviceType,
            session:self.session)
        
        self.browser.delegate = self;
        
        self.assistant = MCAdvertiserAssistant(serviceType:serviceType,
            discoveryInfo:nil, session:self.session)
        
        // tell the assistant to start advertising our fabulous chat
        self.assistant.start()
    }
    
    @IBAction func sendChat(sender: UIButton) {
        // Bundle up the text in the message field, and send it off to all
        // connected peers
        
        let msg = self.messageField.text.dataUsingEncoding(NSUTF8StringEncoding,
            allowLossyConversion: false)
        
        var error : NSError?
        
        self.session.sendData(msg, toPeers: self.session.connectedPeers,
            withMode: MCSessionSendDataMode.Unreliable, error: &error)
        
        if error != nil {
            print("Error sending data: \(error?.localizedDescription)")
        }
        
        self.updateChat(self.messageField.text, fromPeer: self.peerID)
        
        self.messageField.text = ""
    }
    
    func updateChat(text : String, fromPeer peerID: MCPeerID) {
        // Appends some text to the chat view
        
        // If this peer ID is the local device's peer ID, then show the name 
        // as "Me"
        var name : String

        switch peerID {
        case self.peerID:
            name = "Me"
        default:
            name = peerID.displayName
        }
        
        // Add the name to the message and display it
        let message = "\(name): \(text)\n"
        self.chatView.text = self.chatView.text + message
        
    }
    
    @IBAction func showBrowser(sender: UIButton) {
        // Show the browser view controller
        self.presentViewController(self.browser, animated: true, completion: nil)
    }
    
    func browserViewControllerDidFinish(
        browserViewController: MCBrowserViewController!)  {
        // Called when the browser view controller is dismissed (ie the Done 
        // button was tapped)
        
        self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    func browserViewControllerWasCancelled(
        browserViewController: MCBrowserViewController!)  {
        // Called when the browser view controller is cancelled
        
        self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    func session(session: MCSession!, didReceiveData data: NSData!,
        fromPeer peerID: MCPeerID!)  {
        // Called when a peer sends an NSData to us
        
        // This needs to run on the main queue
        dispatch_async(dispatch_get_main_queue()) {
            
            var msg = NSString(data: data, encoding: NSUTF8StringEncoding)
            
            self.updateChat(msg, fromPeer: peerID)
        }
    }
    
    // The following methods do nothing, but the MCSessionDelegate protocol 
    // requires that we implement them.
    func session(session: MCSession!,
        didStartReceivingResourceWithName resourceName: String!,
        fromPeer peerID: MCPeerID!, withProgress progress: NSProgress!)  {
            
        // Called when a peer starts sending a file to us
    }
    
    func session(session: MCSession!,
        didFinishReceivingResourceWithName resourceName: String!,
        fromPeer peerID: MCPeerID!,
        atURL localURL: NSURL!, withError error: NSError!)  {
        // Called when a file has finished transferring from another peer
    }
    
    func session(session: MCSession!, didReceiveStream stream: NSInputStream!,
        withName streamName: String!, fromPeer peerID: MCPeerID!)  {
        // Called when a peer establishes a stream with us
    }
    
    func session(session: MCSession!, peer peerID: MCPeerID!,
        didChangeState state: MCSessionState)  {
        // Called when a connected peer changes state (for example, goes offline)

    }

}

This code does the following things:

  1. It adds a class extension to the ViewController class, which makes
    it conform to the MCBrowserViewControllerDelegate and
    MCSessionDelegate protocols, as well as creating four new properties,
    including one to handle the Multicast Peer components:
    • The MCBrowserViewController is a prebuilt view controller for handling and negotiating browsing for connections.
    • The MCAdvertiserAssistant is another prebuilt class for both advertising and negotiating creating a connection and session.
    • The MCSession is the object holding the sessions once it has been negotiated.
    • MCPeerID represents your peer id for the session.
  2. In the viewDidLoad method, the code initializes all the Multicast Peer objects before telling the advertiser to start advertising its availability.
  3. In the sendChat method, the sendData(toPeers:error:) message on the session is the real meat of the application. This is the method used to send data—in our application, a string from the text field.
  4. Finally, session(didReceiveData:fromPeer:) is the delegate method that is called when the session receives any data—in the case of our application. It appends the new data to the text view, showing the received text to the text view.

Now if you and another friend both (or you, using two devices) run the application, you should have an amazing chat app!

Editor’s note: Dive deeper into Swift with “Swift Development with Cocoa” by Paris Buttfield-Addison, Jonathon Manning, and Tim Nugent.

Public domain handset illustration courtesy of Internet Archive.

tags: , , , , ,