15 Jul
2011
After working with Bluetooth using GameKit framework for a while in my current project I decided to write a basic tutorial that should help anybody to get started with connecting two iOS devices together. First let's talk about why and when should you use Bluetooth. When I came up with the idea of an app that requires two devices to communicate I had to make a choice of technology that will connect the devices. First thing that came to my mind was to use the Bump API. Why? Because the bumping idea is actually pretty cool + everything is set up for you and you can send data between two devices in just a couple lines of code. But wait - how did you say Bump works? Over the internet? Right... and what if the user does not have any connection? That's when your app becomes completely useless. So why not use GameKit API instead? I know the name suggests that it has been made for games but what stops you from using it for anything else? Everything is set up for you and you can send data between two devices in a few lines of code as well. Let's take a look how.
First you need to import the GameKit framework to your project. In XCode 4 you need to go to you target Build Phases tab and add GameKit.framework to the Link with Binary Libraries list. Then you need to set up a GKSessionDelegate. You need to carefully decide which of your classes is gonna be the delegate because if you pick the wrong one you end up having to rewrite large parts of your code as I did. The basic question is - when do you need to send and receive data. If it is just a one-off occasion you can set a certain view controller as the delegate but if you need the iPhones to stay connected all the time and the user navigates between multiple views it's probably the best idea to set your ApplicationDelegate as GKSessionDelegate and GKPeerPickerControllerDelegate.
A good tip if your're doing this is to make a macro that allows you to access the ApplicationDelegate from anywhere in your code. Obviously don't forget you have to #import the header file anywhere you need to use this. With this you can access any property of the delegate just by UIAppDelegate.anyProperty and you don't have to write the lengthy piece of code every time.
#define UIAppDelegate ((HelloWorldAppDelegate *)[UIApplication sharedApplication].delegate)
So first thing - the class we will use needs to conform to two protocols as shown below - GKSessionDelegate for handling the session and GKPeerPickerControllerDelegate for estabilishing the connection. Also it is a good idea to set up three properties as show below.
@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate, GKSessionDelegate, GKPeerPickerControllerDelegate> {
GKPeerPickerController *connectionPicker;
GKSession* connectionSession;
NSMutableArray *connectionPeers;
}
@property (retain) GKSession* connectionSession;
@property (nonatomic, retain) NSMutableArray *connectionPeers;
@property (nonatomic, retain) GKPeerPickerController* connectionPicker;
Then we need to properly initialize all those so add this somewhere to your code (didFinishLaunching or viewDidLoad).
connectionPicker = [[GKPeerPickerController alloc] init];
connectionPicker.delegate = self;
//NOTE - GKPeerPickerConnectionTypeNearby is for Bluetooth connection, you can do the same thing over Wi-Fi with different type of connection
connectionPicker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;
connectionPeers = [[NSMutableArray alloc] init];
Now we need to add methods that are needed to conform to the GKPeerPickerControllerDelegate. There is nothing much to do there, you can just copy them and change the session ID to ID your app will use.
#pragma mark - GKPeerPickerControllerDelegate
- (GKSession *)peerPickerController:(GKPeerPickerController *)picker sessionForConnectionType:(GKPeerPickerConnectionType)type
{
// Create a session with a unique session ID - displayName:nil = Takes the iPhone Name
GKSession* session = [[GKSession alloc] initWithSessionID:@"com.myapp.connect" displayName:nil sessionMode:GKSessionModePeer];
return [session autorelease];
}
// Tells us that the peer was connected
- (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session
{
// Get the session and assign it locally
self.connectionSession = session;
session.delegate = self;
[picker dismiss];
}
And now comes the most important part - GKSessionDelegate. The principle for sending data is to convert them into NSData. If you are really sure you will need to send just, say, one NSString than you can encode it directly and just send it. But if you need to send anything more complicated I strongly recommend using the technique with an array and NSKeyedArchiver because it leaves plenty of space for later changes and allows you to send different chunks of data. As you will see this is even probably the easiest method for distinguishing what you actually received - you always get encoded array and when you recreate the array you can test whatever elements in it. Note that you could also use NSDictionary if you need to. And don't forget that if you send some custom classes they need to conform to the NSCoding protocol!
//Method for sending data that can be used anywhere in your app
- (void)sendData:(NSArray*)data
{
NSData* encodedArray = [NSKeyedArchiver archivedDataWithRootObject:data];
[UIAppDelegate.connectionSession sendDataToAllPeers:encodedArray withDataMode:GKSendDataReliable error:nil];
[encodedArray release];
}
And now the method for receiving data.
#pragma mark - GKSessionDelegate
// Function to receive data when sent from peer
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context
{
NSArray *receivedData = [NSKeyedUnarchiver unarchiveObjectWithData:data];
//Handle the data received in the array
[receivedData release];
}- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state {
if (state == GKPeerStateConnected) {
// Add the peer to the Array
[connectionPeers addObject:peerID];
// Used to acknowledge that we will be sending data
[session setDataReceiveHandler:self withContext:nil];
//In case you need to do something else when a peer connects, do it here
}
else if (state == GKPeerStateDisconnected) {
[self.connectionPeers removeObject:peerID];
//Any processing when a peer disconnects
}
}
And finally two methods to call when you want to connect or disconnect usable from anywhere in your app.
- (void)connectToPeers:(id)sender
{
[UIAppDelegate.connectionPicker show];
}
- (void)disconnect:(id)sender
{
[UIAppDelegate.connectionSession disconnectFromAllPeers];
[UIAppDelegate.connectionPeers removeAllObjects];
}
With this tutorial you should be able to set up basic Bluetooth communication between two iOS devices. Obviously there is a lot more in the documentation, take this just as basics that I have learnt somewhere else and slightly refined them.