Sobre IOS5, xml y como reconocer códigos de barras usando ZbarSDK

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

 Explico que significa cada cosa. Los import son básicos, necesario para que Xcode entienda que es eso de ZBarReaderDelegate y NSXMLParserDelegate. Los delegados en IOS5 es como usar una interfaz en java, es decir, el XXXDelegate tiene una serie de métodos que debes implementar (no todos) en el fichero ViewController.m
Me gusta esto de los delegados, pero en mi opinión debe mejorar por parte de apple ya que NO te dice cuales son los métodos que puedes implementar, ni su firma ni nada. Debes averiguar tú por tu cuenta y riesgo cuales son los métodos y cual es su firma. Mal apple, mal.
Bueno, como he dicho antes, apple eligió para su api de desarrollo SAX, lo que significa que para poder parsear un mensaje xml del tipo:
<return>esto es un mensaje</return>
necesitas tener declarado en la interfaz dos variables, una de tipo boolean que setearemos a true cuando SAX detecte que ha llegado a <return> la volveremos a cambiar a false cuando detectemos </return>.
returnString sera una variable que almacenará la cadena “esto es un mensaje”. Que no se nos olvide declarar el @property y sintetizarla en el fichero implementación.
Ahora vamos a ver como es el fichero implementación, viewController.m
Para parsear adecuadamente la respuesta xml que nos da el servicio web, tenemos que implementar al menos tres métodos que nos provee NSXMLParserDelegate, a saber, didStartElement, didEndElement, parse y parseErrorOccurred.
Este metodo se va a encargar de parsear el comienzo del mensaje xml, en la que si detectamos la cadena que nos interesa, debemos inicializar la memoria que necesitaremos para almacenar el contenido del mensaje y setear su booleano a true.

–  (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);

}

}

 Este metodo es invocado por el delegado cuando ha ocurrido algún error de comunicación física entre cliente y servidor.

– (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!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s