Software Development

Create your own web service for an iOS app, part five

The final installment in our iOS series on creating a web service covers how to exchange data between devices using iOS 7's Multipeer Connectivity Framework.
 
apple_1280x960_021014.jpg
 Image: CBS
 
This series shows how to create the web service backend for an iOS app I created called iGlobe. Here's what we've accomplished so far:
  
  • In part one, we created the web database, the web service backend, and the iOS frontend (Storyboard);
  • In part two, we focused on connecting to the web service and fetching actual data;
  • In part three, we explored creating and storing tags in order to exchange them; and
  • In part four, we covered handling multiple users, including storing gamers' credentials.
  • In this final installment of our series, we put all of the pieces together and show how to exchange data between devices. 

    Note: I had to change course from my original plan of featuring the Bump API in part five of this series, but it was announced that support for Bump ended Jan. 31, 2014.

    P2P

    We'll use iOS 7's Multipeer Connectivity Framework to communicate between two devices. We'll only be exchanging string data for now, but you can exchange many other data formats. 

    We have our user's list in a tableview. Users must be able to post data to the tag table. This is where the Tag Class comes in. Create a new NSObject subclassed file and name it Tag. Now replace its code with the following:

     

    #import <Foundation/Foundation.h>
    
    @interface Tag : NSObject {
    	NSNumber *rglatitude;
    	NSNumber *rglongitude;
    	NSString *originUdid;
    	NSString *destintyUdid;
    	NSString *rgcountry;
        NSString *sender;
    	NSString *receiver;
        
    }
    @property(nonatomic,copy)NSString *destintyUdid;
    @property(nonatomic,copy)NSNumber *rglatitude;
    @property(nonatomic,copy)NSNumber *rglongitude;
    @property(nonatomic,copy)NSString *originUdid;
    @property(nonatomic,copy)NSString *rgcountry;
    @property(nonatomic,copy)NSString *sender;
    @property(nonatomic,copy)NSString *receiver;
    
    -(id)initWithOriginUdid:(NSString*)oudid
    			 rglatitude:(NSNumber*)lati
    			rglongitude:(NSNumber*)longi;
    
    -(id)initWithSender:(NSString*)senderi
               receiver:(NSString*)receiveri
                 rglatitude:(NSNumber*)lati
    			rglongitude:(NSNumber*)longi
              rgcountry:(NSString*)rgcountri;
    
    
    -(id)initWithOriginUdid:(NSString*)oudid
    			 destintyUdid:(NSString*)dudid
    			   rglatitude:(NSNumber*)lati
    			rglongitude:(NSNumber*)longi
    			  rgcountry:(NSString*)rgcountri;
    
    @end
    
    #import "Tag.h"
    
    @implementation Tag
    
    -(id)initWithOriginUdid:(NSString*)oudid
    			 rglatitude:(NSNumber*)lati
    			rglongitude:(NSNumber*)longi
    {
    	NSLog(@"TAG INIT");
    	if ( (self = [super init]) == nil )
            return nil;
    	
    	self.rglatitude = lati;
    	self.rglongitude = longi;
    	self.originUdid = oudid;
    	return self;
    }
    
    -(id)initWithSender:(NSString*)senderi
               receiver:(NSString*)receiveri
             rglatitude:(NSNumber*)lati
            rglongitude:(NSNumber*)longi
              rgcountry:(NSString*)rgcountri
    {
        NSLog(@"TAG INIT OF TYPE SENDER RECEIVER");
    	if ( (self = [super init]) == nil )
            return nil;
    	
    	self.receiver = receiveri;
    	self.rglatitude = lati;
    	self.rglongitude = longi;
    	self.sender = senderi;
        self.rgcountry = rgcountri;
    	return self;
    }
    
    -(id)initWithOriginUdid:(NSString*)oudid
    		 destintyUdid:(NSString*)dudid
    			 rglatitude:(NSNumber*)lati
    			rglongitude:(NSNumber*)longi
    			  rgcountry:(NSString*)rgcountri
    {
    	NSLog(@"TAG INIT");
    	if ( (self = [super init]) == nil )
            return nil;
    	
    	self.destintyUdid = dudid;
    	self.rglatitude = lati;
    	self.rglongitude = longi;
    	self.originUdid = oudid;
    	self.rgcountry = rgcountri;
    	return self;
    }
    
    @end
     

    Now we have a class that creates Tag objects, and we have given it different initializers. Every time a user wants to create a tag, they will have to use one of these methods. Users will create tags by bumping phones, and each time users bump their phones they will be able to exchange tags. This is where the Multipeer Connectivity Framework comes in.

    The grand scheme will be:

    1. Add the Multipeer Connectivity Framework to our project.
    2. Add code to create a connection.
    3. Add the code to exchange data.

    The first step is the easiest--select the Blue Project folder atop your File Navigator on the leftmost pane. From the tabs that appear on the Main Editor Window, select Build Phases. Click the + button, type Multipeer, and select the Multipeer Framework. This will add the necessary files to your project.

    For step 2, in your AppDelegate.h add the following lines:

     

    #import <MultipeerConnectivity/MultipeerConnectivity.h>
    extern NSString *const kServiceType;
    @property (strong, nonatomic) MCSession *session; 
    @property (strong, nonatomic) MCPeerID *peerId;
    extern NSString *const DataReceivedNotification;
    - (void)sendDataToPeer;
     

    We're importing the proper framework and declaring a couple of constant strings, a few properties, and the method that will be called to send the data to the other user. The two properties are required to make the connection: a session property and a peerID property. 

    In your AppDelegate.m add the following lines:

     

    NSString *const kServiceType = @"datashare";
    @property (strong, nonatomic) MCAdvertiserAssistant *advertiserAssistant;
    NSString *const DataReceivedNotification = @"com.santiapps.iGlobe:DataReceivedNotification";
     

    We'll use a string identifier for the service type, an advertiser assistant to advertise that we are running Multipeer Connectivity, and an NSNotification identifier to call when data has been exchanged successfully. Add this protocol to the interface declaration: 

     

    <MCSessionDelegate>
     

    Now that we have a way to connect, let's work on the code for actually doing so and exchanging the data we need to exchange.

    Our AppDelegate will manage the session, and our MapViewController will initiate and end the data exchange. In order for the MapViewController to do those things, it needs to talk to the AppDelegate, which will manage the connection.

    In the AppDelegate's appDidFinishLaunchingWithOptions method add the following lines: 

     

    // 1
    NSString *peerName = [[UIDevice currentDevice] name];
    self.peerId = [[MCPeerID alloc] initWithDisplayName:peerName]; 
    // 2

    self.session =[[MCSession alloc] initWithPeer:self.peerId securityIdentity:nil encryptionPreference:MCEncryptionNone]; 
    self.session.delegate = self;
    // 3
    self.advertiserAssistant =
[[MCAdvertiserAssistant alloc] initWithServiceType:kServiceType discoveryInfo:nil session:self.session];
    
    // 4
    [self.advertiserAssistant start];
     

    Here we are assigning a unique name to peerName, initting one of the peers to be used with that peerName, and initting a session with that peer. We set the AppDelegate to be the delegate to the peer connection object.

    Finally, we create our advertiser assistant, which will advertise our availability when needed to other devices, and we start it so we can be discoverable.

    The AppDelegate will be notified when important protocol events occur. Let's add the methods required by this delegate protocol.

    Just below the appDidFinishLaunchingWithOptions method add the following methods:

     

    # pragma mark - Add bump-p2p
    -(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
        NSString *gotUser = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"gotUser: %@", gotUser);
        [[NSNotificationCenter defaultCenter] postNotificationName:DataReceivedNotification object:nil];
    }
     

    This method receives the data exchanged. Once we get it, we decode it--in this case because we are simply getting an NSString that was UTF8 encoded. After it is received, we post a notification to the center reporting that we received it.

     

    -(void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID{
    }
    
    -(void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error{
    }
    
    -(void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID
    withProgress:(NSProgress *)progress{
    }
    
    -(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
    }
     

    These delegate methods are used for advanced features that we don't use in this tutorial. The methods are included here so you can see the many features available to developers when receiving data.

     

    -(void)sendDataToPeer {
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        if ([defaults objectForKey:@"storedUser"]) {
            NSData *myUser = [[defaults objectForKey:@"storedUser"] dataUsingEncoding:NSUTF8StringEncoding];
            NSError *error;
            [self.session sendData:myUser toPeers:[self.session connectedPeers] withMode:MCSessionSendDataReliable error:&error];
        } else {
            NSLog(@"ERROR");
        }
    }
     

    In the end, the sendDataToPeer method takes the storedUser object from NSUserDefaults, encodes it into NSData, and sends it to the other peer.

    So who calls these methods? The class we want to invoke these methods is the one the user will be interacting with when he or she decides to share the data with a user. 

    In our MapViewController class add the following lines:

     

    #import <MultipeerConnectivity/MultipeerConnectivity.h>
    <MCBrowserViewControllerDelegate>
     

    We import the Multipeer Connectivity Framework and adopt the protocol.

    Now we add the methods that will be called, perhaps at the touch of a button:

     

    #pragma bump-p2p
    -(void)viewWillAppear:(BOOL)animated{
        [super viewWillAppear:animated];
        [self dataReceived:nil];
    }
    
    -(void)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController{
        [browserViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
    -(void)browserViewControllerDidFinish:(MCBrowserViewController*)browserViewController{
        [browserViewController dismissViewControllerAnimated:YES completion:^{
            [self sendData];
            [self showMessage:@"didFinish"];
        }];
    }
     

    We set up delegate methods of what will happen when the browserViewController is cancelled or dismissed. The BrowserViewController will pop up when the user taps for a connection and display the peers available for connectivity. It also requires a delegate, which will be the MapViewController.  This should be familiar from working with Cancel and Done buttons.

     

    -(void)sendData {
        AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        [delegate sendDataToPeer];
    }
     

    To send data we need to call our delegate's sendDataToPeer method:

     

    -(void)showMessage:(NSString*)message{
            [[[UIAlertView alloc] initWithTitle:@"DEBUG" message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }
     

    Keep the user notified of what is going on by using a simple UIAlertView to present the user with information:

     

    -(IBAction)sendButtonPressed:(id)sender {
        AppDelegate *delegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
            if ([[delegate.session connectedPeers] count] == 0) {
                MCBrowserViewController *browserViewController = [[MCBrowserViewController alloc] initWithServiceType:kServiceType session:delegate.session];
                browserViewController.delegate = self;
                [self showMessage:@"No Peers"];
                [self presentViewController:browserViewController animated:YES completion:nil];
            } else {
                [self sendData];
                [self showMessage:@"Peers: Sending Data"];
            }
    }
     

    This is the main method in MapViewController regarding peer connectivity. It uses the AppDelegate to check if there are any peers available. If there are peers available, it creates a MCBrowserViewController in order to display them to the user. If there are no peers available, it presents the correct UIAlertView; otherwise, it shows a different message and begins sending data.

     

    -(void)dataReceived:(NSNotification *)notification{
        [self showMessage:@"dataReceived called"];
    } 
     

    The dataReceived method is called when the data is received, and it shows the appropriate method.

    We must take care of some NSNotification setup and clean up. In viewDidLoad:

     

    [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(dataReceived:) name:DataReceivedNotification object:nil];
     

    And in viewDidUnload we unregister:

     

    [[NSNotificationCenter defaultCenter] removeObserver:self];
     

    Run this on your device, and use a friend's device to exchange data between phones or tablets.

    Need help?

    We hope you enjoyed this long multi-part tutorial. Drop us a line if you need help with the steps described in this series. 

     

    About

    My name is Marcio Valenzuela and I am from Honduras. I have been coding in iOS for 5 years and I previously worked on web applications in ASP and PHP/mySQL. I have a few apps up in the app store and am currently working on a couple of Cocos2d games...

    0 comments

    Editor's Picks