Según hacia el master de desarrollo sobre IOS5, mas claro tenia que lo mas importante iba a ser como reconocer el dichoso código de barras y enviar la información a un backend.
Después de googlear un poco me encontré esta librería, ZbarSDK http://zbar.sourceforge.net/iphone/sdkdoc/
hay muchas mas, como zxing, http://code.google.com/p/zxing/ que tienen una licencia free, open source en la que puedes distribuir el código en tus futuros proyectos. Creo que Zbar tiene una licencia un tanto mas oscura, cosa que no me di cuenta cuando empece a jugar con ella. De todas formas yo quería algo para poder construir el primer prototipo, la tecnología subyacente es lo de menos.
Una vez averiguado que es posible capturar un código de barras usando IOS, lo siguiente es como enviar la información necesaria para el backend y para ello lo mejor es usar un servicio web con el que intercambiar información usando mensajes xml o json.
Use al principio xml por la facilidad que tiene de forma nativa IOS para procesarlos. Para los que hayan visto SAX se darán cuenta que es muy muy parecido. Para los que no lo hayan visto, SAX procesa el fichero xml usando eventos, es decir, cuando llega una etiqueta de comienzo, como por ejemplo <MENSAJE> se ejecutara un método DidStartElement en el que solo tendremos que detectar esa cadena de caracteres y hacer lo que sea con ella. Lo mismo cuando llegue algo como </MENSAJE>, se ejecutará un método DidEndElement, el cual, como su nombre indica, tendremos que hacer algo cuando llega el fin de mensaje. De la misma forma también se puede procesar lo que hay entre <MENSAJE>esto es un mensaje</MENSAJE>. Sax fue elegido como implementación de referencia para procesar ficheros xml por parte de apple porque puede ser mucho mas eficiente para procesar mensajes xml muy muy grandes, mucho mas eficiente que DOM por ejemplo. La diferencia principal entre los dos es que DOM tiene que cargar el árbol completo en memoria y sax no, solo carga lo que va leyendo secuencialmente.
Para reconocer codigos de barras en IOS, lo primero es descargar el SDK de la url indicada anteriormente, e instalarlo como se indica en el mismo documento. No voy a explicar como lo hice yo pq solo tuve que leer sus indicaciones. Es muy sencillo y os llevara unos pocos minutos.
Lo que si voy a poner es código IOS para controlar la cámara.
ViewController.h
#import <UIKit/UIKit.h>
#import “MBProgressHUD.h”
@interface ViewController : UIViewController < ZBarReaderDelegate,NSXMLParserDelegate >
{
//necessary to parse xml payload
NSMutableData * xmlData;
//neccesary to parse the possible error
NSMutableString * faultString;
BOOL esperandoFaultString;
//neccesary to parse message from logIn and logOut methods webservice
BOOL esperandoReturn;
NSMutableString * returnString;
//neccesary to parse and save an item
BOOL esperandoItem;
BOOL esperandoISBN;
BOOL esperandoDescripcionItem;
BOOL esperandoPrecioItem;
BOOL esperandoNumTotalItem;
NSMutableString * descripcionItem;
NSMutableString * precioItem;
NSMutableString * numeroTotalItem;
NSMutableString * isbnItem;
NSXMLParser * parser;
}
@property(nonatomic,strong) NSMutableData * xmlData;
@property(nonatomic,strong) NSMutableString * faultString;
@property(nonatomic,strong) NSMutableString * returnString;
@property(nonatomic,strong) NSMutableString * descripcionItem;
@property(nonatomic,strong) NSMutableString * precioItem;
@property(nonatomic,strong) NSMutableString * numeroTotalItem;
@property(nonatomic,strong) NSMutableString * isbnItem;
@property(nonatomic,strong) NSXMLParser * parser;
– (IBAction) scanButtonTapped;
@end
– (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:@”return”] && !esperandoReturn)
{
self.returnString = [[NSMutableString alloc] init];
esperandoReturn=TRUE;
}
}
Este metodo se encarga de parsear el contenido del mensaje.
– (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
//cuando ha llegado <RETURN>, he seteado a true esta variable, por lo que puedo añadir el contenido de string en la variable inicializada anteriormente.
if (esperandoReturn)
{
NSLog(@”foundCharacters ANTES DE AÑADIR EN returnString: %@”,returnString);
[returnString appendString:string];
NSLog(@”foundCharacters: DESPUES DE AÑADIR string: %@ returnString: %@”,string,returnString);
}
}
– (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@”Error de parseo.” message: [parseError description] delegate:nil cancelButtonTitle:@”Ok” otherButtonTitles:nil];
[alert show];
}
Este metodo es invocado cuando llega algo del tipo </, es decir fin de etiqueta. Tenemos que detectar lo que nos interesa e indicar al dispositivo que tiene que devolver la memoria que le han prestado. Si no lo hiciéramos tendríamos un memory leak y eventualmente nos quedaríamos sin memoria disponible en el dispositivo provocando el cierre inesperado de la aplicación.
– (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqual:@”return”])
{
NSLog(@”didEndElement. llega return: %@”,elementName);
esperandoReturn=FALSE;
self.returnString=nil;
NSLog(@”retornoString: %@”,returnString);
}
Puede que os pregunteis que esto que he puesto solo sirve para parsear la conexión entrante que nos envía el servidor, pero como demonios se envía al petición al servidor? muy fácil, hay una serie de métodos que hay que implementar.
Lo primero es un método que lance la petición al servidor web, por ejemplo voy a postrar un método que hace una operación de logIn a mi servidor.
– (void) checkLogIn
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString * _email = [defaults objectForKey:@”email_preference”];
NSLog(@”email seleccionado: %@”,_email);
//C57963F62776530E8701B22C6CC72FF4D2AD29EB44FB6EBF0BD7A390C79856DF legajo del usuario LEGAJOR
if (_email==NULL)
{
[defaults setObject:@”alonsoir@gmail.com” forKey:@”email_preference”];
[defaults synchronize];
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@”Error” message:@”NO HAY USUARIO SELECCIONADO. ” delegate:nil cancelButtonTitle:@”Ok” otherButtonTitles:nil];
[alert show];
//NSLog(@”NO HAY USUARIO SELECCIONADO. ABORTANDO SINCRONIZACION. “);
//return;
}
else
{
NSMutableString * cadena = [[NSMutableString alloc] init];
[cadena appendString:@”http://192.168.1.36:8080/cxf-demo/StoreImpl/logIn?email=”%5D;
[cadena appendString:_email];
NSLog(@”cadena: %@”,cadena);
NSURL *url = [NSURL URLWithString:cadena];
//creo la peticion, ignorando lo que podria tener cacheado localmente y con un tiempo maximo de espera de 5 segundos
NSMutableURLRequest * request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5];
//inicializo el objeto xml que va a transportar el payload desde el servidor
if (self.xmlData == nil)
{
NSLog(@”Initializing xmlData payload on checkLogin…”);
self.xmlData = [[NSMutableData alloc] init ];
}
//creo la conexion de manera no bloqueante…
[NSURLConnection connectionWithRequest:request delegate:self];
}
}
Ya solo me queda que este método se invoque justo después que el dispositivo iOS termine de cargar mi interfaz, en el método viewDidLoad
– (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//el legajo se cogera del properties…
[self checkLogIn];
}
Con eso hemos hecho la petición al servidor, que ocurre cuando hemos terminado de hacer la petición? que una serie de métodos toman el control. Por ejemplo, si ha habido un problema de comunicación física con el servidor, el método didFailWithError se ejecutará inmediatamente, deberemos devolver los recursos inmediatamente .
– (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@”didFailWithError. Error al acceder a la URL.\n %@”,error);
self.xmlData = nil;
self.parser=nil;
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@”Atencion.” message: [error description] delegate:nil cancelButtonTitle:@”Ok” otherButtonTitles:nil];
[alert show];
//[parser release];
}
Este metodo saltara en el mismo momento que el servidor empieza a devolver la info pedida. Lo que haremos será añadir byte a byte la información a la variable de tipo Mutable xmlData.
– (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@”recieving data from webservice…”);
[xmlData appendData:data];
}
Metodo que se ejecuta cuando la conexión con el servidor finaliza. Ya estamos listos para parsear la respuesta que nos ha dado el servidor
– (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Chequeamos en la consola que el contenido del XML es correcto
NSString *xmlCheck = [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
if (xmlCheck != nil)
{
NSLog(@”\nXml recibido.\nxmlCheck:%@\nxmlData:%@”,xmlCheck,xmlData);
// Creamos el parser con la información recibida
if (parser == nil)
{
NSLog(@”initializing parser…”);
parser = [[NSXMLParser alloc] initWithData:xmlData];
}
// Asignamos un delegate al parser
[parser setDelegate:self];
//inicializo lo necesario
esperandoReturn = FALSE;
// Le decimos al parser que empiece a parsear – MÉTODO BLOQUEANTE!!!
[parser parse];
}
else
{
NSLog(@”ERROR, xmlCheck viene vacio…”);
}
NSLog(@”making nil”);
self.xmlData = nil;
self.returnString=nil;
self.parser=nil;
}
Con esto tenemos terminado la parte de comunicación con el servidor usando mensajes xml. Os puedo adelantar que esto funciona muy bien, con un buen rendimiento, pero como todo en la vida, ya hay algo mucho mejor para codificar mensajes sobre un canal físico de datos y se llama JSON. En otro post pondré como tratar la información cuando el servidor envía el payload en formato JSON.
Ahora, como puedo capturar el código de barras usando la cámara de fotos de un smartphone, en este caso un iphone4 de apple?
Muy sencillo, en la interfaz viewController.h hemos declarado que vamos a usar el delegado que la librería ZBarReaderSDK nos provee. El método asignado por el creador de esta librería es:
– (void) imagePickerController: (UIImagePickerController*) reader
didFinishPickingMediaWithInfo: (NSDictionary*) info
{
NSLog(@”didFinishPickingMediaWithInfo…”);
// ADD: get the decode results
id<NSFastEnumeration> results =
[info objectForKey: ZBarReaderControllerResults];
ZBarSymbol *symbol = nil;
// EXAMPLE: do something useful with the barcode data
for(symbol in results) break;
if (symbol.data == nil)
{
NSLog(@”codigo no capturado…”);
}
else
{
NSLog(@”codigo capturado: %@”,symbol.data);
//invoco el ws con un método muy parecido al método checkLogIn descrito anteriormente.
[self checkGetItem:symbol.data];
}
// ADD: dismiss the controller (NB dismiss from the *reader*!)
[reader dismissModalViewControllerAnimated: YES];
}
Este metodo es el invocado cuando queremos capturar un código de barras, si os fijáis está declarado en el viewController.h y es necesario enlazarlo con la pulsación de un botón. Asumo que eso lo sabe hacer quien haya programado algo antes con IOSx.
– (IBAction) scanButtonTapped
{
NSLog(@”scanButtonTapped…”);
// ADD: present a barcode reader that scans from the camera feed
ZBarReaderViewController *reader = [ZBarReaderViewController new];
reader.readerDelegate = self;
reader.supportedOrientationsMask = ZBarOrientationMaskAll;
//[reader showHelpWithReason:@”INFO”];
ZBarImageScanner *scanner = reader.scanner;
[scanner setSymbology: ZBAR_I25
config: ZBAR_CFG_ENABLE
to: 0];
reader.readerView.zoom = 1.0;
// present and release the controller
[self presentModalViewController: reader animated: YES];
}
Y con eso se acaba esta primera parte. Mas adelante pondré como parsear la respuesta en formato JSON en IOS.
También quiero explicar como hacer un servicio web usando apache-cxf, spring, caches concurrentes, threads e hibernate.
Saludos y comentad!