Sharing is Fun

Mobile Development Comments (0)

One of the main strengths of the iOS device family is connectedness. From one small (or, in the iPad’s case, relatively small),  powerful device you can access content from many sources anywhere on the internet. Not only that, but many of these sources allow you to edit that content in a collaborative manner.  Google Docs, Facebook account, connected services provided by apps, you name it – that little device in your hands enables you to take your world with you wherever you go.

While this idea of connectedness is obviously powerful, there is another facet of connectedness we at Float feel is underused at this point is that of local connectedness–that is, sharing information directly with devices in proximity, typically done over shared wifi or Bluetooth connection. Examples of this connectedness might share of a document stored on your device with another, or playing a multi-player game with the changing game state being transferred over a local connection to facilitate the gameplay. It’s easy to make a jump from sharing something like this to using the same facility to share something for learning. Essentially, though, anything that requires transmission of data (be it pixel data, sound data, or a custom data format for an app) can be facilitated by this idea of local connectedness.

While this idea of transferring information between devices is attractive, the thought of implementing such functionality might daunt at first. How to discover and connect to networks? How to discover other devices are on those networks? How to convert the data to be sent into a format that can be sent? How then to send that converted data over the network in such a way that the receiving device can parse and “reconstruct” that data in an intended manner. While writing such functionality from scratch would be a time-consuming and laborious task, the iOS SDK (from version 4.1) provides a collection of API calls within the GameKit framework that simplify device to device communication. We at Float have experience using this framework, so we thought we’d share insights with you to help get you started on building your own local connectedness enhanced applications.

The core class that enables easy device to device communication is GKSession. Essentially, a GKSession object, when instantiated within your app, creates an ad-hoc network between any other devices that have a GKSession object instantiated with the same session id. So, to think of it another way, a network comprising all devices that are running your app. Devices connected to a session can then be considered being peers on this session or ad-hoc network. The session object can then be utilized to send data between these connected peers with two key methods. While there a variety of ways that these sessions, and peers, can be configured, we have put together a simple app to illustrate how one of these configurations can be used to create a simple, but directed, application of GKSession to provide some useful connected functionality.

This sample app will provide an interface that allows a user of a device to select an image from their saved photos and then choose to “Share” a selected photo with all connected peers. Additional to this, connected devices that “receive” an image will then be able to save the image to their own saved photos. We chose a view based application to keep things simple and added a toolbar to the main view controller containing two buttons to facilitate both the image picking functionality and the share/save functionality. And this we added an image view to the view controller to display both images selected from saved photos and images received from another device. Finally, an activity indicator was added as a visual cue to a user that an incoming image is being transmitted to the device. With the basic interface in place, let’s look at the key sections of code that facilitate the connected nature of this simple, yet effective sharing app.

First off, we need to initialize and start our session, the key object that controls connecting to other devices and transmitting data to other connected devices. The first thing we need to do is to make sure our main view controller implements the GKSessionDelegate protocol – this protocol provides a set of functions that are triggered when connection “events” occur on the session object. Essentially, when other devices attempt to connect to the created session, and when the state of those connections change state. With this protocol in place, the next thing to do is to create the actual session object. As the main purpose of the app is to transmit and receive images, we’re just going to create the session on app startup and have it be present throughout – so, in our viewDidLoad function we place the following code:

theSession = [[GKSession alloc] initWithSessionID:@"imageSenderApp" displayName:nil sessionMode:GKSessionModePeer];
theSession.delegate = self;
[theSession setDataReceiveHandler:self withContext:nil];
theSession.available = YES;

Note that theSession variable is a property of the view controller (allowing us to maintain the connection while the app is running), as are receiving and shareMode (variables that are used to determine the current “state” of the app). While this is only a few lines of code, this is all we need to create a usable session for our app. The key pieces of the puzzle here are setting up the delegate of the session as self (more on this shortly), setting the dataReceiveHandler as self, and setting the property to YES. This means that the session will accept any connection request from a peer (i.e. another device running the app).

The next item on the agenda is to enable other devices (peers) to connect to our session, and be able to connect to other peers. Both of these are achieved by implementing GKSessionDelegate methods. First, we need to implement the didReceiveConnectionRequest method – this is triggered when another peer asks for a connection to the session. Because we are developing an “open” app (ie. anyone can send or receive images) we will essentially accept any connection request. Here is the code:

- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID {
NSLog(@"receive connection from peer");
NSError *error = nil;
if (![self.theSession acceptConnectionFromPeer:peerID error:&error]){
NSLog(@"%@",[error localizedDescription]);
}
}

As we see in the method signature, we get an incoming session and the id of the peer requesting a connection. In a more fully featured app, we could reject the incoming request based on some app criteria (for example, don’t allow more than 10 connected peers for a session), but for our purposes we want to have the session-accept the request (it will keep track of the connected peers as part of the object’s base functionality) – this is done by sending an acceptConnectionFromPeer message to the session object (passing the incoming peer id as an argument).

Now, to connect to other devices in the session, we need to implement the didChangeState method. This method is fired when the session detects a peer changing state in relation to the session. Several state changes cause this method to fire, but the key one we are interested in is the GKPeerStateAvailable state change – this will be triggered when the session first detects a peer becoming available (in our case, when the app is started and viewDidLoad executes). Here we want to establish a connection to that peer, again through a method on the session object. The code:

case GKPeerStateAvailable:{
NSLog(@"gk avail");

[self.theSession connectToPeer:peerID withTimeout:10.0];
break;
}

As we can see from this code, when the method is triggered as a result of a peer becoming available, we then send a connectToPeer message to the session with id of the peer in question (sent as part of the call to the method). Note that this, in turn, will cause our earlier didReceiveConnectionRequest method to be fired on the other peer, thus setting up the two-way communication required by our app. The implementation of these two methods is all that is required to establish a communication pathway between all devices running our app on the same wifi network or with Bluetooth turned on and within range.

With the connection of the devices achieved, let’s look at what is needed to send data back and forth between those devices. In broad terms, what we need to do when the user clicks the Share button sends the data of the current image to all the peers connected to the session (all other devices running the app). The session method that will facilitate this is the sendDataToAllPeers method. This method takes an NSData object and sends that object to all peers. We have two issues. First, how can we convert the image into an NSData object? Thankfully, there is an API call that does exactly that – UIImagePNGRepresentation. So,

NSData *pixelData = UIImagePNGRepresentation([imageView image]);

gives us a “sendable” object based on the displayed image (imageView is the image view controller) to be sent to all the connected peers. The second issue we come across is a limitation of the GKSession object. A data object sent over a session cannot exceed 86k (with the recommendation not to exceed 10k). So, the question becomes, how do we split up our larger pixelData object into smaller “chunks” to send, and how do we ensure that those “chunks” arrive at the receiving peers in the correct order to be reassembled into the image to be displayed. We can use the dataWithBytesNoCopy method with the length property of the data to facilitate the “chunking” making a copy of successive segments of the pixelData object within a loop, then sending each of these segments with our call to sendDataToAllPeers, like so:

[theSession sendDataToAllPeers:slice withDataMode:GKSendDataReliable error:&theError]

where slice is the current “segment” of the larger object (see the initShare method in the attached code files to see how this fits into the method). The aspect of this call to sendDataToAllPeers that answers our “how do we ensure the data gets to the connected peers in order” question, is specifying GKSendDataReliable as the data mode – this tells the session object that the order in which we send data “packets” is the order in which they should arrive at a connected peer. Specifying this data mode guarantees that the data packets will be received in order by all connected peers, thus allowing us to be sure that the received packets will be reassembled into an image on a connected peer. The final piece of data we need to send is an indicator that the image is complete, done by sending an NSData object representing a certain string (EOD in our case) immediately after the loop that controls the chunking of the pixelData object. The receiving peer can then detect and use this specific string as a signal that the image is complete and can be “put back together”.

The final piece in our puzzle is receiving this image data from another peer. We set this up when we specified self as the dataReceiveHandler in our initial code that created our session. By doing this, the session object knows to trigger the receiveData method in the view controller when it detects data being sent to the app from a connected peer, taking the sent data object as a parameter. Because we know that the incoming data will be split into several “chunks” we will assume that an initially received data object is the “start” of an image and use the data to set up a view controller property with the incoming data, like so:

receivePixelData = [[NSMutableData alloc] initWithData:data];

Note that receivePixelData is an NSMutableData, allowing further data to be appended to it. So, as subsequent packets are received by the method we can append the incoming data to this object:

[receivePixelData appendData:data];

Finally, we then look for the complete message in the incoming data as a prompt to “close off” our data object and use it to construct an image to be displayed in our image view. The code that achieves this is:

[imageView setImage:[UIImage imageWithData:receivePixelData]];

(See the receiveData method in the attached code files to see how this fits into the method as a whole).

That concludes our walkthrough of the code that facilitates the creation and management of a session in an app, then utilizing that session to send data back and forth between devices. The relative brevity of this code highlights how easy it is to integrate what is a powerful and flexible tool into your app’s functionality. Keep in mind that while we are using the session to send image data between devices, that NSData is used as a data “vehicle” means virtually any data can be transmitted between devices. So, your imagination really is the only limit to the ways in which iOS devices can connect and share media, assets or really any information at all with one another – have fun!

Download the source for this project here: ImageSenderCode

If you are exploring the concept of local connectedness in your mobile learning strategy or using GameKit framework in your app development, Float wants to know. Add a comment below and tell us about it.

Follow Float
The following two tabs change content below.

Mark Tovey

Mark is a Mobile Developer at Float with a B.S. in Applied Science (Information Science). Mark has a varied background in the I.T. industry, having worked as both a mainframe programmer/analyst and web developer over the course of his career, in areas as diverse as homeland security, education, insurance, e-commerce, and e-learning. This breadth of experience has given him a deep understanding of all of the phases of the software development life cycle. In the recent past, Mark has developed many content managed websites using open source software, including the Oyez Project, as well as developing interactives for clients such as The Adler Planetarium and Shure Inc. Mark is also an instructor at Bradley University, teaching an introductory course to the Javascript and Actionscript scripting languages.

» Mobile Development » Sharing is Fun
On March 28, 2011
By
, ,

Leave a Reply

Your email address will not be published. Required fields are marked *

« »