Understanding Multipeer Connectivity Framework in iOS 7 – Part 1
Editor’s note: This is part 1 of the Multipeer Connectivity series.
Multipeer Connectivity Framework
on nearby devices only, meaning devices that use the same network infrastructure (such as Wi-Fi or Bluetooth), so don’t expect it to work for devices existing in long distance. With Multipeer Connectivity, one device is able to get connected to many other devices and communicate at the same time with all of them. A single device is called apeer. What actually takes place when a connection between two peers is established, is that aMultipeer ConnectivitySession
discoverone each other. Discovery is the first phase that takes place when Multipeer Connectivity is used in applications. How discovery is being made? Let’s suppose we have only two devices we want to get connected. One device at least must work as abrowser, in order to search for other existing devices, and the second device must be discoverable by telling that it’s out there and wants to connect to someone, or in other words the second device mustadvertise
Regarding the browsing functionality especially, Apple provides two ways to use it. The first and easy one, is a browsing UI built-in directly into the framework, which when is called a modal view is displayed listing all available and connected devices to the one that works as a browser. The second way offers greater flexibility to developers, as it’s a totally programmatic way, so one can implement customized browsing according to the needs of the application. Later on we will use the first way only, as this is an introductory tutorial about this framework.
Once the discovery of other peers has been done, the peer that locates the advertiser sends a message asking to establish a connection. If the second one (the advertiser) accepts that, then a session is created and both peers are ready to exchange data.
Talking about data, there are three kinds of data that can be sent and received using the Multipeer Connectivity framework. These are:
- Messages (including text, images, and everything else that can be converted to aNSData
- Streaming
- Resources
reliableand theunreliable
Generally, the Multipeer Connectivity framework offers classes and libraries for high-level development, so no C or other kind of low-level programming is required. Under the hood, many features are built-in ready to be used, taking away any hassle caused by implementing or addressing any network-related issues. In this tutorial we won’t consume any more time in theoretical presentation of the framework, therefore you are strongly advised to visitApple’s documentationand do some study, and of course, watch theWWDC 2013Session 708 video. Note that for testing the Multipeer Connectivity framework you need to have at least two devices, or use one device and the iPhone Simulator.
To see how easily the Multipeer Connectivity feature can be put in action and how it lets devices communicate, just keep reading!
Demo App Overview
In this tutorial we will create a sample application for demonstrating the most important aspects of the Multipeer Connectivity framework. I mentioned previously that there are three kinds of data that can be exchanged using it: Messages, streaming and resources. In the demo application that we are going to implement, we will see how to send messages, meaning NSData objects from device to device, and how to share resources, such as files. However, before we start building, we need to take a quick look on what exactly the demo app is about.
tabbedapplication, with three tabs in total. Starting on purpose from the last one, titledMy Connections, we will create a view controller to manage the peer (device) naming, the advertising and all the connections (the sessions actually) of the device. Specifically, using atext field, we’ll be able to set a custom name for the device that will appear to other peers. Abuttonwill display the default browser UI when it’s tapped, while aswitchcontrol will toggle the advertising functionality of the device. In atable viewthey will be listed all of our connections. Under the table view there will be one morebutton, which will be used to disconnect the device from the session. The next figure presents a synopsis of the view controller:
Chat Boxand it will be used for sending text messages among devices using the Multipeer Connectivity. It will consist of atext fieldwhere the message will be written, atext viewwhere the whole chat will appear, and a couple ofbuttons, one for sending the message and one for cancelling it. Here is a screenshot of it:
File Sharing. Here atable viewwill contain a list of some (sample) files. When a row is selected, then anaction sheet
I presented all the view controllers of the demo application starting from the third tab in purpose, as the view controller behind it is the most important for us in this tutorial. Through it, we will see how the discovery and session establishment phases are handled and managed.
classfor managing all the framework-related objects and tasks. In the application delegate (AppDelegate) class then we will initialize an object of this class. Also, through the application delegate we will access this object across the application. After all, it’s a terrible programming practice to develop something multiple times, instead of build it just once and use it as a tool.
So, after having walked through the application we are going to develop, it’s time to start working on it. You may download the two sample files at the end of this tutorial if you want to build step by step. Alternatively, get the full demo application provided for your own convenience, but stay put for all code details.
Demo App Creation
Launch Xcode and create a new project by clicking on the respective button at the left side of the Welcome screen:
Tabbed Applicationoption in theApplicationcategory, under theiOS
MCDemoin theProduct Namefield, but feel free to name it otherwise if you’d like. Besides that, make sure that in theDevicesdrop down menu, theiPhone
Finally, select a destination for your project and click on the Create button to end this guide. The project is now ready.
Adding New Tab
As I have already said, the application will consist of three tabs in total, but by default there are only two. So, our first task is to create a new tab and to connect it with a new class we will add to the project. Also, further than that we will set all tab titles and icons.
UIViewControllerclass to the project for the third tab that we will add in a while. On theProject Navigation, Ctrl-click or right-click on the MCDemo group and from the popup menu select theNew File…
iOSsection, make sure that theCocoa Touchcategory is selected. Then, select theObjective-C class
Classfield, set theConnectionsViewControllervalue. Also, make sure that theUIViewControllervalue is filled in theSubclass of
ConnectionsViewController.handConnectionsViewController.m
Main.storyboard
Tab Bar Controllerscene, and without leaving the mouse button drag the mouse on the new scene we just added to the interface. Release both the Ctrl key and the mouse button. A black, popup window will appear, where under theRelationship Seguesection you must click on theview controllers
ConnectionsViewControllerclass we added earlier as the class for the new view controller. To do so, click on the new view controller scene, and in theUtilities Pane, open theIdentity Inspectorand in theCustom Classsection start writingConnect…in theClass
Attributes Inspectorand under theBar Titlesection set the title in theTitle
Set the following titles:
- First View Controller:Chat Box
- Second View Controller:File Sharing
- Connections View Controller:My Connections
Note that any visual configuration on Interface Builder made in this tutorial is targeted for 4″ screen devices, such as iPhone 5 or 5S. If you want to make the application run on older devices with a 3.5″ screen size, just play around by applying the suggested constraints by Xcode and you’ll easily manage to have it running in these devices as well.
Connections View Controller: Setup the Interface
Now that the third tab, along with its respective view controller have been added, it’s time to start building our application beginning from the view controller of the last tab, the Connections View Controller. Our first mission is to setup its interface, as well as to declare and connect any required IBOutlet properties and IBAction methods. So, while being in the Interface Builder, drag and drop the controls presented right next from the Objects Library into the Connections View Controller scene. Note that for each control are provided all the properties you should modify:
• UITextField
• Frame: X=20, Y=20, Width=280, Height=30
• Placeholder: The device name displayed to others…
• UILabel
• Frame: X=20, Y=63, Width=180, Y=21
• Text: Visible to others?
• UISwitch
• Frame: X=251, Y=58, Width=51, Y=31
• State: ON
• UIButton
• Frame: X=94, Y=92, Width=132, Y=30
• Title: Browse for devices
• UITableView
• Frame: X=0, Y=130, Width=320, Y=352
• UIButton
• Frame: X=121, Y=490, Width=78, Y=30
• Text: Disconnect
• Enabled: False (Unchecked)
After adding all the above, here is how your scene should look like:
ConnectionsViewController.h
@property ( weak , nonatomic ) IBOutlet UITextField *txtName ;
@property ( weak , nonatomic ) IBOutlet UISwitch *swVisible ;
@property ( weak , nonatomic ) IBOutlet UITableView *tblConnectedDevices ;
@property ( weak , nonatomic ) IBOutlet UIButton *btnDisconnect ;
Also, add these IBAction methods:
- ( IBAction ) browseForDevices : ( id ) sender ;
- ( IBAction ) toggleVisibility : ( id ) sender ;
- ( IBAction ) disconnect : ( id ) sender ;
Main.storyboardto connect them to the appropriate controls. To do that, make sure that theDocument Outline Paneis shown. Next, Ctrl-click or right-click on theConnections View Controller – Connectionsobject, and a black, popup window will appear. Next to each property (and each IBAction method) there is a circle.Click on this circle next to an IBOutlet property, and without releasing the mouse button, drag and drop to the respective control on the scene. The following figure illustrates exactly that:
The connections should be done as follows:
- ThetxtName
- TheswVisible
- ThetblConnectedDevices
- ThebtnDisconnect
For the rest objects on the scene we don’t need to use IBOutlet properties.
Using the same method as above, connect the declared IBAction methods to the appropriate controls. Here is how you should match the methods with the controls:
- ThebrowseForDevices:method should connect to the first UIButton (titledBrowse for devices) object.
- ThetoggleVisibility:
- Thedisconnect:
The interface for the Connections View Controller is now ready. We will discuss about the functionality and the purpose of each control we just added later. For the time being, we have to focus for first time on the Multipeer Connectivity framework details.
A Multipeer Connectivity Framework-Related Class
Right now we are going to do a big turn, as we will leave behind us the Interface Builder and any visual setup, and we will focus totally on code. In this section our goal is to create a new class, in which we will implement all framework-related logic, we will use any required framework classes and we will perform any necessary tasks. Note that it’s not required by the framework to do so, however this is the most convenient way for this sample application to perform app-wide tasks without having to re-write the same code more than once.
New File…
Objective-C classoption as the template for the new file. Click Next to proceed. Next, at theSubclass offield, set theNSObjectvalue. Right above, in theClassfield, you must write the name of the new class. I named itMCManager, and it would be a good idea you name it the same, just to follow properly this tutorial.
MCManager.handMCManager.m
MCManager.h
#import
As you see, I didn’t add the framework manually to the project, but I directly used it. The compiler will do that for us, thanks to a new feature that incorporates, namedAuto Linking.
Now, modify the interface header line to adopt theMCSesssionDelegate
@interface MCManager : NSObject
After doing so, declare the next objects:
@property ( nonatomic , strong ) MCPeerID *peerID ;
@property ( nonatomic , strong ) MCSession *session ;
@property ( nonatomic , strong ) MCBrowserViewController *browser ;
@property ( nonatomic , strong ) MCAdvertiserAssistant *advertiser ;
ThepeerIDobject represents the device and it contains various properties needed for the discovery and session establishment phases. Thesessionobject is the most important one, as it represents the session that the current peer (the device that this app will run) will create. Any data exchanging and any communication details are controlled by this object. Thebrowserobject is actually representing the default UI provided by Apple for browsing for other peers, and we will use it for this purpose. For more advanced handling of the browsing feature of the framework, Apple provides a programmatic alternative way, but for now it’s out of the scope of this tutorial. Finally, there is theadvertiser
Notice that the classes of all these objects belong to the Multipeer Connectivity framework. You’ll see how we will use them, but for now let’s declare some public methods that we’ll need later as well:
- ( void ) setupPeerAndSessionWithDisplayName : ( NSString * ) displayName ;
- ( void ) setupMCBrowser ;
- ( void ) advertiseSelf : ( BOOL ) shouldAdvertise ;
MCManager.mfile, and for starters add the nextinit
- ( id ) init {
self = [ super init ] ;
if ( self ) {
_peerID = nil ;
_session = nil ;
_browser = nil ;
_advertiser = nil ;
}
return self ;
}
At this point, Xcode should normally be complaining and displaying some warnings. Those are because we haven’t implemented the public methods yet, and also because we didn’t add any delegate methods of theMCSessionDelegate
- ( void ) session : ( MCSession * ) session peer : ( MCPeerID * ) peerID didChangeState : ( MCSessionState ) state {
}
- ( void ) session : ( MCSession * ) session didReceiveData : ( NSData * ) data fromPeer : ( MCPeerID * ) peerID {
}
- ( void ) session : ( MCSession 服务器托管网 * ) session didStartReceivingResourceWithName : ( NSString * ) resourceName fromPeer : ( MCPeerID * ) peerID withProgress : ( NSProgress * ) progress {
}
- ( void ) session : ( MCSession * ) session didFinishReceivingResourceWithName : ( NSString * ) resourceName fromPeer : ( MCPeerID * ) peerID atURL : ( NSURL * ) localURL withError : ( NSError * ) error {
}
- ( void ) session : ( MCSession * ) session didReceiveStream : ( NSInputStream * ) stream withName : ( NSString * ) streamName fromPeer : ( MCPeerID * ) peerID {
}
MCSessionStateConnected,MCSessionStateConnectingandMCSessionStateNotConnected. The last state is valid even when a peer gets disconnected from our session. Generally this delegate method is called when the peer state is modified. The second delegate method is called when new data arrives from a peer. Remember that three kinds of data can be exchanged; messages, streaming and resources. This one is the delegate for messages. The next couple of methods are called when a resource is received, and finally the last one is invoked for incoming streams. In this tutorial we’ll use all of them, except for the last one.
Let’s go to the public method implementation now. Begin with the next code fragment, and we’ll talk about it in a while.
- ( void ) setupPeerAndSessionWithDisplayName : ( NSString * ) displayName {
_peerID = [ [ MCPeerID alloc ] initWithDisplayName :displayName ] ;
_session = [ [ MCSession alloc ] initWithPeer : _peerID ] ;
_session . delegate = self ;
}
First of all, thepeerIDobject is initialised, as everything is based on it. ThedisplayName
Next, we initialise thesessionobject, the most important one as everything depends on it. We provide it with thepeerID
Let’s continue:
- ( void ) setupMCBrowser {
_browser = [ [ MCBrowserViewController alloc ] initWithServiceType : @"chat-files" session : _session ] ;
}
serviceType
- Must be 1–15 characters long.
- Can contain only ASCII lowercase letters, numbers, and hyphens.
The second parameter is the session object initialised on the previous method.
One more public method to go:
- ( void ) advertiseSelf : ( BOOL ) shouldAdvertise {
if ( shouldAdvertise ) {
_advertiser = [ [ MCAdvertiserAssistant alloc ] initWithServiceType : @"chat-files"
discoveryInfo :nil
session : _session ] ;
[ _advertiser start ] ;
}
else {
[ _advertiser stop ] ;
_advertiser = nil ;
}
}
We are going to use this public method for toggling the advertising feature of the device. As you notice, the parameter defines whether the device should advertise itself or not, depending on the settings of our app. Remember that we added a UISwitch object for making our peer visible/invisible to others.
When we want the advertiser on, we initialise it and we start it. Note theserviceType
So, for now our class is ready to be used as a tool. We’ll visit it many times along the way, as we’ll add code to the delegate methods of the session. Maybe not everything makes sense to you, but don’t worry, all will become clear upon usage.
One last thing we have left to do, and that is to go to theAppDelegate.h
@property ( nonatomic , strong ) MCManager *mcManager ;
Don’t forget however to import the class:
#import "MCManager.h"
After that, open theAppDelegate.mfile and inside theapplication:didFinishLaunchingWithOptions:
- ( BOOL ) application : ( UIApplication * ) application didFinishLaunchingWithOptions : ( NSDictionary * ) launchOptions
{
// Override point for customization after application launch.
_mcManager = [ [ MCManager alloc 服务器托管网 ] init ] ;
return YES ;
}
We are now in position to use our class through the object of the application delegate. Let’s go ahead to put Mutlipeer Connectivity in action!
The Discovery Phase
ConnectionsViewControllerclass we previously created, therefore we need to make it conform to theMCBrowserViewControllerDelegateprotocol in order to be able to handle the browser. Go to theConnectionsViewController.h
#import
Next, modify the interface header as follows:
@interface ConnectionsViewController : UIViewController
ConnectionsViewController.m
@property ( nonatomic , strong ) AppDelegate *appDelegate ;
Don’t worry if Xcode shows an error. You just have to make the following import:
#import "AppDelegate.h"
mcManagerobject of the application delegate. How? To see that go straight ahead to theviewDidLoad
- ( void ) viewDidLoad
{
[ super viewDidLoad ] ;
_appDelegate = ( AppDelegate * ) [ [ UIApplication sharedApplication ] delegate ] ;
[ [ _appDelegate mcManager ] setupPeerAndSessionWithDisplayName : [ UIDevice currentDevice ] . name ] ;
[ [ _appDelegate mcManager ] advertiseSelf : _swVisible . isOn ] ;
}
In the first line we instantiate theappDelegateobject using thesharedApplicationclass method. After doing so, we are able to call any required public methods of themcManagerobject, and that’s exactly we are doing right next. ThesetupPeerAndSessionWithDisplayName:method is being called, and as we are in the initialisation of our class, as the display name of our device we specify its actual name. During execution, if no custom name is set to the text field, then this name will appear to the other peers. Finally, we call theadvertiseSelf:
Let’s implement the browser view controller appearance now, by adding the necessary code to thebrowseForDevices:IBAction method. Add the next code fragment in yourConnectionsViewController.m
- ( IBAction ) browseForDevices : ( id ) sender {
[ [ _appDelegate mcManager ] setupMCBrowser ] ;
[ [ [ _appDelegate mcManager ] browser ] setDelegate :self ] ;
[ self presentViewController : [ [ _appDelegate mcManager ] browser ] animated :YES completion :nil ] ;
}
setupMCBrowserpublic method of theMCManagerclass. Next, we set self (this class) as its delegate and finally we modally present it. After having this method implemented, the button with titleBrowse for devices
MCBrowserViewControllerDelegate. So, turn back to Xcode and add these two methods:
- ( void ) browserViewControllerDidFinish : ( MCBrowserViewController * ) browserViewController {
[ _appDelegate . mcManager . browser dismissViewControllerAnimated :YES completion :nil ] ;
}
- ( void ) browserViewControllerWasCancelled : ( MCBrowserViewController * ) browserViewController {
[ _appDelegate . mcManager . browser dismissViewControllerAnimated :YES completion :nil ] ;
}
When tapping on the Done button of the view controller, we simply want to dismiss it, and similarly want the browser to act when the Cancel button is tapped. Therefore, both of these methods contain the same code, just for dismissing the browser view controller. The first method is the delegate for the Done button, and the second one is the delegate for the Cancel button.
Before going any further, just note how we access thebrowser
_appDelegate . mcManager . browser
Now, both buttons of the browser view controller are perfectly working. However, the Done button is disabled and you cannot test it yet, unless another device is discovered and gets connected to the current one. Before we see that though, let’s implement two more functionalities: How to set a custom name for the device and how to enable/disable the advertiser.
Peer Display Name And The Advertiser State
ConnectionsViewController.hfile and adopt theUITextFieldDelegate
@interface ConnectionsViewController : UIViewController
Now, in theviewDidLoadmethod inside theConnectionsViewController.m
- ( void ) viewDidLoad
{
. . .
[ _txtName setDelegate :self ] ;
}
textFieldShouldReturn:delegate method of the text field, because we want the keyboard to be disappeared when the Return button is tapped and thepeerIDobject of the MCManager class to get the name we set to the text field. However, in theviewDidLoadmethod we have already initialised both thepeerIDand thesessionobjects, so first we need to set them to nil and then reinitialise them using the specified name by calling thesetupPeerAndSessionWithDisplayName:
- ( BOOL ) textFieldShouldReturn : ( UITextField * ) textField {
[ _txtName resignFirstResponder ] ;
_appDelegate . mcManager . peerID = nil ;
_appDelegate . mcManager . session = nil ;
_appDelegate . mcManager . browser = nil ;
if ( [ _swVisible isOn ] ) {
[ _appDelegate . mcManager . advertiser stop ] ;
}
_appDelegate . mcManager . advertiser = nil ;
[ _appDelegate . mcManager setupPeerAndSessionWithDisplayName : _txtName . text ] ;
[ _appDelegate . mcManager setupMCBrowser ] ;
[ _appDelegate . mcManager advertiseSelf : _swVisible . isOn ] ;
return YES ;
}
Note that we check if the advertiser is on, and if that’s the case we first stop it and then we set the respective object to nil.
There is an important fact that I should point out here. Once our device is connected to another peer, we shouldn’t change its display name, as a session would have been already established and any data exchange might be in progress. Therefore, in order to protect us from changing name while the device is connected, we will keep the text field disabled. Only when there is no connection to other peers the text field will be enabled, and we are going to associate this functionality with the Disconnect button later on. More specifically, once a connection gets established, the text field will become disabled, and when the Disconnect button is used to stop a connection, it will become enabled again. So, for now, just keep this in mind and let’s move forward.
Let’s make the switch control now to enable and disable the advertiser. Implement the following IBAction method:
- ( IBAction ) toggleVisibility : ( id ) sender {
[ _appDelegate . mcManager advertiseSelf : _swVisible . isOn ] ;
}
advertiseSelf
If you test the application now (in two devices or in one device and in the Simulator), you can play around with the peer discovery and the two more functionalities we added here. Here are some screenshots:
When a nearby device is found:
Using the switch to turn on/off the advertising feature of the second device:
Changing the display name of the device:
Note that the display name of the device, as well as the advertiser’s state can be changed, as we still haven’t made any connections. In the next part we are going to do exactly that. We will also see how we can get notified about the connected peers, how to show the other peer’s display name on the table view and how to make the Disconnect button work.
Making a Connection
Making a connection is really simple. In the browser view controller, you just have to tap on a nearby device’s name and wait until it gets connected. The view controller displays the status of the connections, changing from the Not Connected to Connecting and finally to the Connected state. Once this happens, the Done button becomes enabled.
On the other device an alert view similar to the next one appears, prompting the user to accept or decline the connection:
MCSessiondelegate methods, which we implemented previously in theMCManager
MCManager.mfile and locate thesession:peer:didChangeState:method. This one is called when a new connection takes place, and is our job to handle the info that provides. What we are going to do in our case is quite simple: As this is a class other than the one that we use to manage our connections (theConnectionsViewControllerclass), we will post anotification
- ( void ) session : ( MCSession * ) session peer : ( MCPeerID * ) peerID didChangeState : ( MCSessionState ) state {
NSDictionary *dict = @ { @"peerID" : peerID ,
@"state" : [ NSNumber numberWithInt :state ]
} ;
[ [ NSNotificationCenter defaultCenter ] postNotificationName : @"MCDidChangeStateNotification"
object :nil
userInfo :dict ] ;
}
At first, we create aNSDictionaryobject and we set thepeerIDandstateparameter values as its contents. Next, we post a notification with theMCDidChangeStateNotificationname and thedict
However, for the time being, only the half job has been done. We need to make theConnectionsViewControllerclass to observe for this notification and act appropriately when one arrives. So, open theConnectionsViewController.mfile, and head to theviewDidLoad
- ( void ) viewDidLoad
{
. . .
[ [ NSNotificationCenter defaultCenter ] addObserver :self
selector : @selector ( peerDidChangeStateWithNotification : )
name : @"MCDidChangeStateNotification"
object :nil ] ;
}
peerDidChangeStateWithNotification:, and it’s a custom private method that we will declare right now. Go to the private section of the interface, and add the next declaration:
@interface ConnectionsViewController ( )
. . .
- ( void ) peerDidChangeStateWithNotification : ( NSNotification * ) notification ;
@end
Next, we must implement it, but let’s discuss about it for a moment. What do we want to do when a new connection is being made? Simply to add the connected peer’s display name to the table view, to enable the Disconnect button and to disable the text field (I explained earlier why). On the other hand, when a peer is disconnected, we simply have to remove its name from the table view, and if no other peers exist, to disable the Disconnect button and enable the text field.
In order to do all that, we need an array that will be used as the datasource for our table view. Therefore, let’s declare and initialise this array, and then we will be able to implement the private method.
Go once again at the private section of the interface and add the next declaration:
@interface ConnectionsViewController ( )
. . .
@property ( nonatomic , strong ) NSMutableArray *arrConnectedDevices ;
@end
viewDidLoad
- ( void ) viewDidLoad
{
. . .
_arrConnectedDevices = [ [ NSMutableArray alloc ] init ] ;
}
Also, in order our table view to respond to everything we want, we must set our class as its delegate and its datasource. So, while being in theviewDidLoad
- ( void ) viewDidLoad
{
. . .
[ _tblConnectedDevices setDelegate :self ] ;
[ _tblConnectedDevices setDataSource :self ] ;
}
UITableViewDelegateand theUITableViewDatasource. To fix this, go to theConnectionsViewController.h
@interface ConnectionsViewController : UIViewController
Now we are totally ready to implement our private method that will be called when a notification arrives. Open once again theConnectionsViewController.m
- ( void ) peerDidChangeStateWithNotification : ( NSNotification * ) notification {
MCPeerID *peerID = [ [ notification userInfo ] objectForKey : @"peerID" ] ;
NSString *peerDisplayName = peerID . displayName ;
MCSessionState state = [ [ [ notification userInfo ] objectForKey : @"state" ] intValue ] ;
}
peerIDandstate), and we keep the display name of the connected peer to a NSString object. Let’s keep going:
- ( void ) peerDidChangeStateWithNotification : ( NSNotification * ) notification {
. . .
if ( state != MCSessionStateConnecting ) {
if ( state == MCSessionStateConnected ) {
[ _arrConnectedDevices addObject :peerDisplayName ] ;
}
else if ( state == MCSessionStateNotConnected ) {
if ( [ _arrConnectedDevices count ] > 0 ) {
int indexOfPeer = [ _arrConnectedDevices indexOfObject :peerDisplayName ] ;
[ _arrConnectedDevices removeObjectAtIndex :indexOfPeer ] ;
}
}
}
}
First of all, we perform any action only when the current state is other than theMCSessionStateConnecting. So, if currently the state matches to theMCSessionStateConnected, then we add the peer display name to thearrConnectedDevicesarray. Otherwise, if the state is equal to theMCSessionStateNotConnected
- ( void ) peerDidChangeStateWithNotification : ( NSNotification * ) notification {
. . .
if ( state != MCSessionStateConnecting ) {
. . .
[ _tblConnectedDevices reloadData ] ;
BOOL peersExist = ( [ [ _appDelegate . mcManager . session connectedPeers ] count ] == 0 ) ;
[ _btnDisconnect setEnabled : ! peersExist ] ;
[ _txtName setEnabled :peersExist ] ;
}
}
connectedPeersmethod of thesession
The method is ready, but if you run the application you’ll see no results at all. Why not? Because we still haven’t implemented the minimum required table view delegate and datasource methods. Let’s do it now. Right next, you are given all these methods at once:
- ( NSInteger ) numberOfSectionsInTableView : ( UITableView * ) tableView {
return 1 ;
}
- ( NSInteger ) tableView : ( UITableView * ) tableView numberOfRowsInSection : ( NSInteger ) section {
return [ _arrConnectedDevices count ] ;
}
- ( UITableViewCell * ) tableView : ( UITableView * ) tableView cellForRowAtIndexPath : ( NSIndexPath * ) indexPath {
UITableViewCell *cell = [ tableView dequeueReusableCellWithIdentifier : @"CellIdentifier" ] ;
if ( cell == nil ) {
cell = [ [ UITableViewCell alloc ] initWithStyle :UITableViewCellStyleDefault reuseIdentifier : @"CellIdentifier" ] ;
}
cell . textLabel . text = [ _arrConnectedDevices objectAtIndex :indexPath . row ] ;
return cell ;
}
- ( CGFloat ) tableView : ( UITableView * ) tableView heightForRowAtIndexPath : ( NSIndexPath * ) indexPath {
return 60.0 ;
}
There is nothing hard here that needs to be explained. We just display the contents of the table view. If you test the app now and make a connection, you should see something similar to this:
Finally, there is just one more thing to be done and our view controller will be fully functional. That is to make the disconnect button work. Simply, implement thedisconnect:
- ( IBAction ) disconnect : ( id ) sender {
[ _appDelegate . mcManager . session disconnect ] ;
_txtName . enabled = YES ;
[ _arrConnectedDevices removeAllObjects ] ;
[ _tblConnectedDevices reloadData ] ;
}
sessionobject has thedisconnect
Go and test once again the application. After you have a connection established, tap on the Disconnect button and you’ll see that in both devices the other peer’s display name goes away, while the text field becomes enabled again.
At this point, the Connections View Controller is ready! Up to now, we have seen many things regarding the framework, but let’s go ahead for even more interesting stuff!
Setup The Chat Interface
Once a connection is established, we can exchange any allowed data we want. In this part we will work with setting up the First View Controller interface, and later we will implement the chatting feature of the application. So, let’s get started!
Main.storyboard
Then, drag and drop the following controls from the Objects Library and set some of their properties as given below:
• UITextField
• Frame: X=20, Y=20, Width=280, Height=30
• Placeholder: Your message…
• UIButton
• Frame: X=254, Y=58, Width=46, Height=30
• Title: Send
• UIButton
• Frame: X=25, Y=58, Width=48, Height=30
• Title: Cancel
• UITextView
• Frame: X=0, Y=96, Width=320, Height=422
• Text: None
• Background Color: Light Gray
Your scene should now look like this:
FirstViewController.hfile and add these two inside the interface body:
@interface FirstViewController : UIViewController
@property ( weak , nonatomic ) IBOutlet UITextField *txtMessage ;
@property ( weak , nonatomic ) IBOutlet UITextView *tvChat ;
@end
Also, we want an IBAction method for each button, so add these as well:
@interface FirstViewController : UIViewController
. . .
- ( IBAction ) sendMessage : ( id ) sender ;
- ( IBAction ) cancelMessage : ( id ) sender ;
@end
UITextField
@interface FirstViewController : UIViewController
Following the same steps for connecting the IBOutlet properties and the IBAction methods as we did while we were setting the Connections View Controller up, go and connect thetxtMessageproperty to the text field and thetvChatproperty to the text view. Next, connect thesendMessage:IBAction method to the Send button and thecancelMessage:
Let’s Chat
The title of this section speaks on its own for our goal here. We will do any required implementation, so our connected devices using the Multipeer Connectivity framework to be able to chat using text messages.
Let’s begin by doing something common for all three view controllers: To declare and instantiate anAppDelegateobject. Go to theFirstViewController.mfile, and import theAppDelegate.h
#import "AppDelegate.h"
Then, inside the private section of the interface, make this declaration:
@interface FirstViewController ( )
@property ( nonatomic , strong ) AppDelegate *appDelegate ;
@end
Finally, go to theviewDidLoad
- ( void ) viewDidLoad
{
[ super viewDidLoad ] ;
_appDelegate = ( AppDelegate * ) [ [ UIApplication sharedApplication ] delegate ] ;
}
mcManagerobject of the application delegate. We are continuing by implementing thetextFieldShouldReturn:text field delegate method. We need it because we want the Return button of the keyboard to behave just like the Send UIButton, and that is to send the message to other peers. However, before we see this method, and while we are still in theviewDidLoad
- ( void ) viewDidLoad
{
. . .
_txtMessage . delegate = self ;
}
Now we can implement thetextFieldShouldReturn:
- ( BOOL ) textFieldShouldReturn : ( UITextField * ) textField {
[ self sendMyMessage ] ;
return YES ;
}
sendMyMessage
- ( IBAction ) sendMessage : ( id ) sender {
[ self sendMyMessage ] ;
}
- ( IBAction ) cancelMessage : ( id ) sender {
[ _txtMessage resignFirstResponder ] ;
}
They couldn’t be any simpler. The first one also calls thesendMyMessage
Now we can dive in to the core of the desired functionality. We will implement the private method, and we will meet a new method of the framework that allows us to send messages.
Go to the private section of the interface to declare the method:
@interface FirstViewController ( )
. . .
- ( void ) sendMyMessage ;
@end
Now we may proceed to its implementation, so let’s see it first and we’ll discuss later.
- ( void ) sendMyMessage {
NSData *dataToSend = [ _txtMessage . text dataUsingEncoding :NSUTF8StringEncoding ] ;
NSArray *allPeers = _appDelegate . mcManager . session . connectedPeers ;
NSError *error ;
[ _appDelegate . mcManager . session sendData :dataToSend
toPeers :allPeers
withMode :MCSessionSendDataReliable
error : & error ] ;
if ( error ) {
NSLog ( @"%@" , [ error localizedDescription ] ) ;
}
[ _tvChat setText : [ _tvChat . text stringByAppendingString : [ NSString stringWithFormat : @"I wrote:n%@nn" , _txtMessage . text ] ] ] ;
[ _txtMessage setText : @"" ] ;
[ _txtMessage resignFirstResponder ] ;
}
Thesessionobject has thesendData:toPeers:withMode:error:method, which is the one that actually sends a message. The data should be aNSDataobject and that’s exactly is taking place in the first line by using thedataUsingEncoding:
The second parameter of the method must be aNSArray
The third parameter is quite important, as this is the place where we define the mode that our data will be sent. At the beginning of this tutorial, I said that there are two modes, the reliable and the unreliable. This is supposed to be a chat application, so we don’t want to lose any package. Therefore, when calling the method we specify theMCSessionSendDataReliable
The last parameter is the all-time classic error object, which we use to check if any error occurred after we have this method called. Indeed, if any error occurs, we just log its description, as there is no reason to handle it in any other way in our demo app.
Finally, we perform three more tasks. First of all, we display the message to the text view, and to make clear that it’s us who wrote it, we prepend theI wrote:
Further than the new method of thesessionobject we just met, there is nothing difficult or weird. Let’s focus now on what happens when a message is received by a peer. Once that happens, thesession:didReceiveData:fromPeer:delegate method is called, and it’s our responsibility to handle the received message according to our needs. We have implemented this method in theMCManager
Go to theMCManager.mfile, and find thesession:didReceiveData:fromPeer:method. Our approach here is going to be the same to thesession:peer:didChangeState:
Here is its implementation:
- ( void ) session : ( MCSession * ) session didReceiveData : ( NSData * ) data fromPeer : ( MCPeerID * ) peerID {
NSDictionary *dict = @ { @"data" : data ,
@"peerID" : peerID
} ;
[ [ NSNotificationCenter defaultCenter ] postNotificationName : @"MCDidReceiveDataNotification"
object :nil
userInfo :dict ] ;
}
NSDictionaryobject with the provided data and peer as its contents. Next, we post a notification with the given name and send the dictionary along with it. After having added this simple code, we have nothing more to do, so let’s go back to theFirstViewController.m
viewDidLoad
- ( void ) viewDidLoad
{
. . .
[ [ NSNotificationCenter defaultCenter ] addObserver :self
selector : @selector ( didReceiveDataWithNotification : )
name : @"MCDidReceiveDataNotification"
object :nil ] ;
}
ThedidReceiveDataWithNotification:
@interface FirstViewController ( )
. . .
- ( void ) didReceiveDataWithNotification : ( NSNotification * ) notification ;
@end
NSDataobject. From the peer object we’ll get its display name, and we will convert the data into aNSString
Here is the implementation:
- ( void ) didReceiveDataWithNotification : ( NSNotification * ) notification {
MCPeerID *peerID = [ [ notification userInfo ] objectForKey : @"peerID" ] ;
NSString *peerDisplayName = peerID . displayName ;
NSData *receivedData = [ [ notification userInfo ] objectForKey : @"data" ] ;
NSString *receivedText = [ [ NSString alloc ] initWithData :receivedData encoding :NSUTF8StringEncoding ] ;
[ _tvChat performSelectorOnMainThread : @selector ( setText : ) withObject : [ _tvChat . text stringByAppendingString : [ NSString stringWithFormat : @"%@ wrote:n%@nn" , peerDisplayName , receivedText ] ] waitUntilDone :NO ] ;
}
performSelectorOnMainThread:withObject:waitUntilDone:
Our chatting feature is ready! If you want to test it, first establish a connection and then start sending text messages from peer to peer.
Summary
The Multipeer Connectivity framework is a brand-new feature on iOS 7. In this tutorial we gave you a brief introduction of the framework and demonstrated how you can use it to build a simple chat app. There is still a lot to explore. In part 2 of the tutorial series, we’ll continue to work on the project and see how to share file with nearby devices. Stay tuned.
For your reference, you candownload the complete Xcode project from here. As always, feel free to share your thoughts and comments below.
Update:Part 2is now available.
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
ADC API分析 unsigned int IoTAdcRea服务器托管网d(unsigned int channel, unsigned short *data, IotAdcEquModelSel equModel, IotAdcCurBais curB…