Desarrollo iOS: Comunicación Bluetooth mediante GameKit

http://www.migueldiazrubio.com/2011/12/24/desarrollo-ios-comunicacion-bluetooth-mediante-gamekit/

En este artículo vamos a ver paso por paso como implementar comunicación entre varios dispositivos iOS mediante la tecnología Bluetooth.

Como en este caso, una imagen vale más que mil palabras, os adjunto un pequeño video donde se aprecia cual será el comportamiento de la aplicación que vamos a implementar:

Con la salida de GameKit, Apple incorpora una serie de clases que nos permiten establecer todo lo necesario para realizar comunicaciones peer to peer entre dos o mas dispositivos.

Esta comunicación tiene ciertas limitaciones que es necesario conocer:

  • Dado que el Simulador de XCode no soporta Bluetooth, para poder probar nuestra aplicación, necesitaremos dos dispositivos iOS.
  • Al tener que instalar la aplicación en nuestro dispositivo, también necesitaremos pertenecer al iOS Developer Program (es decir fotografiarse con 79€ al año).
  • Requiere como mínimo iOS 3.0 (porque fue en esta versión del SO donde se incorporó GameKit).
  • El número máximo de conexiones entre clientes y un servidor es de 16.
  • El tamaño máximo de un paquete de datos enviado entre dos dispositivos es 87 kilobytes. En caso de necesitar enviar más información será necesario fragmentarla en paquetes de este tamaño o inferiores.

Bueno, después de todas las “pegas” para este ejemplo, vamos con la parte buena, y es que vamos a poder comprobar en estado puro la potencia que proporcionan algunos frameworks de los suministrados con iOS como es en este caso GameKit.framework.

Principalmente vamos a utilizar estas clases del framework:

  • GKPeerPickerController: esta clase nos permite con un par de líneas de código y de forma automática presentar al usuario toda una serie de pantallas que facilitan la solicitud de activación del Bluetooth en caso de que el usuario no la tenga activada, seleccionar al dispositivo al que queremos conectarnos, y finalmente establecer la conexión con el mismo. Una carencia que he encontrado en la utilización de este Picker (evitando implementar nosotros mismos toda la lógica), es que lo textos que ofrece en pantalla están en inglés, y al menos yo, he sido incapaz de poder modificarlos para este ejemplo.
  • GKSession: esta clase gestiona el hilo de comunicación que se establece entre ambos dispositivos, y es a través de dicha sesión a través de donde podremos transmitir información entre ambos dispositivos.
  • GKPeerConnectionState: esta clase nos controla cual es el estado de la conexión con el dispositivo. De esta forma podremos ocultar o mostrar los botones correspondientes a las acciones de Conectar y Desconectar.

Vamos a analizar los diferentes componentes de nuestra pequeña aplicación:

BluetoothViewController.h

#import <UIKit/UIKit.h>
#import <GameKit/GameKit.h>
@interface BluetoothViewController : UIViewController {
GKSession *currentSession;
IBOutlet UITextField *txtMessage;
IBOutlet UIButton *connect;
IBOutlet UIButton *disconnect;
}
@property (nonatomic, retain) GKSession *currentSession;
@property (nonatomic, retain) UITextField *txtMessage;
@property (nonatomic, retain) UIButton *connect;
@property (nonatomic, retain) UIButton *disconnect;
-(IBAction) btnSend:(id) sender;
-(IBAction) btnConnect:(id) sender;
-(IBAction) btnDisconnect:(id) sender;
@end

Nuestro interfaz va a tener únicamente los siguientes elementos:

  • txtMessage: que va a ser una caja de texto UITextView donde podremos indicar el texto que queremos transmitir al otro dispositivo.
  • Connect: que va a ser el botón para establecer la conexión.
  • Disconnect: que nos permitirá desconectar.
  • Send: que nos va a permitir enviar el texto indicado a traves de la sesión abierta.

Como a estas alturas seguro que ya conocéis, el único elemento que no tiene definido un IBOutlet es el botón de Send. Esto es debido a que desde código vamos a necesitar ocultar los botones de Connect y Disconnect, sin embargo el botón Send únicamente lo utilizaremos para lanzar el método btnSend. Por este motivo no es necesario crear el IBOutlet para este elemento.

Ahora vamos a ver los métodos que deberemos implementar en nuestro fichero BluetoothViewController.m

BluetoothViewController.m

En primer lugar deberemos importar la librería GameKit y hacer los synthesize de los properties definidos anteriormente:

#import <GameKit/GameKit.h>
@implementation BluetoothViewController
@synthesize currentSession;
@synthesize txtMessage;
@synthesize connect;
@synthesize disconnect;

Para implementar la lógica del evento Connect, debemos incluir lo siguiente:

-(IBAction) btnConnect:(id) sender {
picker = [[GKPeerPickerController alloc] init];
picker.delegate = self;
picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;
[connect setHidden:YES];
[disconnect setHidden:NO];
[picker show];
}

Simplemente inicializamos la clase GKPeerPickerController, establecemos el tipo de conexión, y lanzamos la interfaz asociada a dicho elemento mediante su método show.

Al utilizar el Picker, y asignarnos a nosotros mismos (self) como delegate del Picker, deberemos implementar los siguientes dos métodos:

- (void)peerPickerController:(GKPeerPickerController *)picker
didConnectPeer:(NSString *)peerID
toSession:(GKSession *) session {
self.currentSession = session;
session.delegate = self;
[session setDataReceiveHandler:self withContext:nil];
picker.delegate = nil;
[picker dismiss];
}
- (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker
{
picker.delegate = nil;
[connect setHidden:NO];
[disconnect setHidden:YES];
}

También vamos a necesitar un método para poder desconectar y asociar al botón Disconnect:

-(IBAction) btnDisconnect:(id) sender {
[self.currentSession disconnectFromAllPeers];
currentSession = nil;
[connect setHidden:NO];
[disconnect setHidden:YES];
}

Al indicar en el método peerPickerController que somos delegate del objeto Session, estamos obligados a implementar los siguientes dos métodos:

- (void)session:(GKSession *)session
peer:(NSString *)peerID
didChangeState:(GKPeerConnectionState)state {
switch (state)
{
case GKPeerStateConnected:
NSLog(@"connected");
break;
case GKPeerStateDisconnected:
NSLog(@"disconnected");
currentSession = nil;
[connect setHidden:NO];
[disconnect setHidden:YES];
break;
}
}
- (void) mySendDataToPeers:(NSData *) data
{
if (currentSession)
[self.currentSession sendDataToAllPeers:data
withDataMode:GKSendDataReliable
error:nil];
}

Por último deberemos implementar la lógica asociada al botón Send, y que nos permitirá utilizar la sesión para comunicar el texto introducido al otro dispositivo. Así mismo, el método receiveData nos permite recoger los datos recibidos en el dispositivo.

-(IBAction) btnSend:(id) sender
{
//---convert an NSString object to NSData---
NSData* data;
NSString *str = [NSString stringWithString:txtMessage.text];
data = [str dataUsingEncoding: NSASCIIStringEncoding];
[self mySendDataToPeers:data];
}
- (void) receiveData:(NSData *)data
fromPeer:(NSString *)peer
inSession:(GKSession *)session
context:(void *)context {
//---convert the NSData to NSString---
NSString* str;
str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Data received"
message:str
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}

Quizás este ejemplo puede parecer muy laborioso, pero claramente se demuestra la gran potencia que ofrecen los frameworks que vienen incluidos en iOS y de los cuales podemos hacer uso en nuestras aplicaciones sin a penas esfuerzo.

Para los que queráis el código completo de la aplicación, podéis descargarlo desde aquí.