martes, 21 de abril de 2015

Lector de Código de Barras y QR


1. Introducción

Con la entrada de Swift y iOS 8 son muchas las novedades que han introducido en framework como MapKit, CoreLocation, PassKit o AVFoundation. 

En este tutorial quiero mostraros como realizar un lector de códigos de barras y códigos QR haciendo uso de la librería AVFoundation todo ello haciendo uso de Swift. 


2.¿Qué es un código de barras? ¿y un código de QR?

Aunque muchos de nosotros lo sepamos y veamos a diario siempre es bueno profundizar en algo que vamos a usar. Por ello vamos a hablar de los mecanismos y características escondidas de estos códigos. 

Empecemos por los códigos de barras. Son códigos basado en la representación mediante líneas paralelas de distinto grosor y espaciado de modo que en conjunto contienen una determinada información. Es decir las barras y los espacios representan pequeñas cadenas de caracteres. De este modo son muy usados para recocer rápidamente un artículo de forma única, global y no ambigua.

Existen multitud de tipos como ha ido evolucionando a lo largo del tiempo desde la primera patente en 1952. Gracias a los códigos de barras se ha podido agilizar el etiquetado de precios, el control del stock de mercancías, ofrecer estadísticas comerciales y  automatizar el registro y seguimiento de productos. Todo ello a un bajo coste, con una probabilidad de error muy baja y ofreciendo flexibilidad y rapidez.


Fig. 1 Ejemplo de código de barras


Con respecto a los códigos QR estos son como una evolución de los simples códigos de barras. Se pueden definir como un código de barras bidimensional. Exactamente son una forma de guardar información en un matriz de puntos. Se caracterizan por tener unos cuadrados en las esquinas para detectar la posición del código al lector. Su nombre QR viene de "Quick Response", es decir su objetivo es leer su contenido a alta velocidad.

Fig. 2 Ejemplo de código QR


Para más información ver:

En el siguiente gráfico os muestro algunos ejemplos. Aunque algunos parecen similares son códigos y estándares diferentes.





 Por curiosidad os dejo una página web donde podéis crear vuestro propio código. En mi caso cree uno QR con la cadena "HolaMundo" como información codificada.

Fig. 3 Código QR: HolaMundo

Enlace: GeneradorCódigos


3. Leer Códigos usando AVFoundation

La librería gracias a funcionalidad implementa en la clase AVCaptureMetaDataOutput permite la detección distintos tipos de códigos. La siguiente lista muestra todos los disponibles en la versión 8 de iOS:


      • AVMetadataObjectTypeUPCECode
      • AVMetadataObjectTypeCode39Code
      • AVMetadataObjectTypeCode39Mod43Code
      • AVMetadataObjectTypeEAN13Code
      • AVMetadataObjectTypeEAN8Code
      • AVMetadataObjectTypeCode93Code
      • AVMetadataObjectTypeCode128Code
      • AVMetadataObjectTypePDF417Code
      • AVMetadataObjectTypeQRCode
      • AVMetadataObjectTypeAztecCode
      • AVMetadataObjectTypeInterleaved2of5Code
      • AVMetadataObjectTypeITF14Code
      • AVMetadataObjectTypeDataMatrixCode


Esta funcionalidad de detección se produce en tiempo real mediante la cámara del dispositivo, a través del stream o fotogramas que esta genera.

Para implementar nuestro escaner se necesario crear una sesión (AVCaptureSession) e implementar un método de su delegado. Este proceso se puede complicar en función de lo que busquemos que realice nuestro escáner. En el ejemplo explicaremos lo esencial para un uso adecuado. El ejemplo consistirá en crear un ScannerViewController es decir un controlador de vista que implemente el lector de código de barras permitiendo una funcionalidad y comunicación mediante delegación.

A continuación explicaremos las principales clases de las que haremos uso:


3.1 AVCaptureSession

Se trata de uno de los objetos claves el cual ayuda a manejar el flujo de datos desde  la etapa de captura a través de el dispositivo de entrada como una cámara o micrófono a la salida como puede ser un archivo de audio o video.

3.2 AVCaptureDevice

Representa al dispositivo físico de captura y las propiedades asociadas a este. Este objeto se usa para configurar las propiedades subyacente al hardware. El dispositivo de captura provee la entrada de datos a AVCaptureSession. Se puede tener mucha flexibilidad cambiando las propiedades de entrada como el enfoque o exposición. 

3.3 AVCaptureInputDevice

Es muy util para captura de datos de dispositivos de entrada. 

3.4 AVCaptureMetadataOutput

Es la clase encargada de interceptar objetos de metadatos de parte de la sesión y enviárselos al delegado. Se puede usar procesar específicos tipos de metadatos incluido los datos de entrada. 

3.5 AVCaptureVideoPreviewLayer

Es una especial CGLayer que ayuda a mostrar datos capturar desde dispositivos de entrada.




4. Clase: ScannerViewController

En este apartado vamos a crear nuestra clase para la lectura de códigos de barras y QR. Explicaremos el código relacionado con respecto al lector de códigos en sí ya que es lo más importante. El resto como voy a dejar la clase para descargarla no hay ningún problema, aún así hay unos detalles que comentaré.

Lo primero es ver las propiedades de la clase. La primera de ella es la referida al delegado que explicaremos más adelante. Las siguientes son todas las relacionadas con la captura. Y por último añadimos algunas de control y el código leído en sí.

    var delegate: ScannerViewControllerDelegate?
    
    var device: AVCaptureDevice?
    var input: AVCaptureDeviceInput?
    var output: AVCaptureMetadataOutput?
    var session: AVCaptureSession?
    var preview: AVCaptureVideoPreviewLayer?
    
    var codeDetected: Bool = false
    var code:String?

    var canBeDisplayed = true

A continuación vamos a analizar la función para la puesta a punto de la cámara por partes. La primera de ella que tenemos abajo es comprobar si el dispositivo cuenta con la cámara. Por ejemplo el emulador no tiene y por lo tanto no podemos probar el lector en él.

        self.device = AVCaptureDevice.
                      defaultDeviceWithMediaType(AVMediaTypeVideo)
        if self.device == nil {
            println("No video camera on this device!")
            //self.dismissViewControllerAnimated(true, completion: nil)
            self.canBeDisplayed = false
            return

        }

En el siguiente paso configuramos el resto de instancias. Lo primero es crear la sesión en caso de que no lo este. A continuación creamos y añadimos la instancia de AVCaptureDeviceInput referida a esa sesión.

El siguiente paso es configurar AVCaptureMetadataOutput conectando a la sesión creada e indicando los tipos de códigos permitidos así como indicando su delegado que será este propio controlador. Por último creamos la capa que mostrara los datos para que ocupe toda la vista. Una vez esta todo correctamente configurados podemos lanzar la sesión con "startRunning()".

        if let s = self.session{
            return
            
        }else{
            self.session = AVCaptureSession()
            
            if let s = self.session{
                
                self.input = AVCaptureDeviceInput.
                             deviceInputWithDevice(self.device, error: nil)
                             as? AVCaptureDeviceInput
                
                if s.canAddInput(self.input) {
                    s.addInput(self.input)
                }
                
                self.output = AVCaptureMetadataOutput()
                self.output?.setMetadataObjectsDelegate(self
                             queue: dispatch_get_main_queue())
                if s.canAddOutput(self.output) {
                    s.addOutput(self.output)
                }
                self.output?.metadataObjectTypes = self.output?.availableMetadataObjectTypes
                
                
                self.preview = AVCaptureVideoPreviewLayer(session: s)
                self.preview?.videoGravity = AVLayerVideoGravityResizeAspectFill
                self.preview?.frame = self.view.frame
                self.view.layer.insertSublayer(self.preview, atIndex: 0)
                
                s.startRunning()
            }

        }



Con esto tenemos implementado nuestro método "setupCamera()". A continuación es necesario implementar el método del protocolo AVCaptureMetadaOutputDelegate. Para ello lo primero es añadirlo en la cabecera.  Este método se llama cada vez que se ha detectada un código.

// MARK:  AVCaptureMetadataOutputObjectsDelegate
    func captureOutput(captureOutput: AVCaptureOutput!, 
                       didOutputMetadataObjects metadataObjects: [AnyObject]!, 
                       fromConnection connection: AVCaptureConnection!) {
        
        if !self.codeDetected{
            
            for data in metadataObjects {
                let metaData = data as! AVMetadataObject
                let transformed = 
                    self.preview?.transformedMetadataObjectForMetadataObject(metaData) 
                    as? AVMetadataMachineReadableCodeObject
                
                if let unwraped = transformed {
                    let code: String = unwraped.stringValue
                    println("CodeBar: " + code)
                    if !(code == ""){
                        self.codeDetected = true
                        self.code = code
                        //self.delegate?.codeDetected(code)
                        
                        //self.dismissViewControllerAnimated(true, completion: nil)
                        self.showAlertCodeDetected(code)
                    }
                }
            }
            
        }

    }

Lo siguiente a mostrar de la clase son el propio protocolo delegado que se crea para el controlador, es decir el protocolo que deberán implementar las clases que hagan uso de nuestro escáner. Y por otro lado funciones y detalles correspondientes a detalles de visualización como pueden ser alertas.

Lo primero es definir el protocolo fuera de la clase relacionado con la propiedad delegate de la clase que al principio se comenta:

     protocol ScannerViewControllerDelegate {
    
        func codeDetected(code: String)
    

     }

Las siguientes funciones son simples funciones para mostrar alertas:

    func showAlertError(){
        
        var alertView = UIAlertView(
            title:"Atention",
            message:"Scanner can't be displayed",
            delegate:self,
            cancelButtonTitle:"OK")
        alertView.tag = 1
        
        alertView.show()
    }
    
    func showAlertCodeDetected(code: String){
        
        var alertView = UIAlertView(
            title:"Code Detected",
            message:"The code is: " + code,
            delegate:self,
            cancelButtonTitle:"Accept",
            otherButtonTitles: "Cancel")
        
        alertView.tag = 0
        
        alertView.show()

    }

Y referidas a estas alertas también se implementa su protocolo denominado UIAlertViewDelegate:

    // MARK:  UIAlertViewDelegate
    
    func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
        
        if(alertView.tag == 0){
            
            if buttonIndex == 0{
                self.delegate?.codeDetected(self.code!)
                self.dismissViewControllerAnimated(true, completion: nil)
            }else if buttonIndex == 1{
                
                self.codeDetected = false
            }
            
            
        }else if(alertView.tag == 1){
        
            self.dismissViewControllerAnimated(true, completion: nil)
        }
    }


Por último añado un botón "Cancelar" a la vista para que en caso de no querer detectar ningún código se pueda pulsar y quitar el escáner. Ese código se puede ver en la propia clase ya que no incumbe al tema del tutorial.

En la siguiente imagen se ve un ejemplo donde se detecta un código de barras. Cuando se detecta muestra una alerta y si aceptamos se llama al método "codeDetected" del protocolo ScannerViewControllerDelegate que estará implementado en la clase que hace uso del escáner recibiendo el código.



Aquí dejo los enlaces tanto de descarga directa como de GitHub.

Enlace descarga directa: ScannerViewController
Enlace GitHub: ScannerViewController


0 comentarios:

Publicar un comentario