An Overview of Qrcode, Barcode, Face and Text detection in iOS

These past few weeks I played with learning how to recognising qrcodes from the camera, but also barcodes, faces and text.

After lots of research I found there are a lot of  great options out there to achieve this, so here is an overview of the different solutions available, both in swift and objective-c.

I. QRCode Reader in Swift with QRCodeReader library

The easiest solution which requires very little coding is taking a library. For Swift there is a very nice library called QRCodeReader.swift  which allows you to recognise qrcodes with a few lines of code. Here are 2 examples of use: one with the qrcode reader controller as a modal view, and the other one within a a subview.

1.1 QRCode reader as a full screen pop up

First create a controller A and add a button to open the qrcode reader modally:

@IBAction func qrCodeTapped(sender: AnyObject) {
        self.performSegueWithIdentifier("scanner", sender: nil)
    }

Then create another controller called `ScannerVC`, and copy paste the following code in it:

import Foundation
import QRCodeReader
import SVProgressHUD

typealias ScannerCallback = (result: ScannerResult?)->()

class ScannerResult{
    var type: String;
    var id: String;
    
    init(type:String, id: String ){
        self.type = type
        self.id = id
    }
}

class ScannerVC: UIViewController, QRCodeReaderViewControllerDelegate
{
    var urlPattern = ".*www.mywebsite.com\\/(.*)\\/(.*)"
    var _callback: ScannerCallback?
    var qrvc: QRCodeReaderViewController!
    
    override func viewDidLoad() {
        qrvc = QRCodeReaderViewController(cancelButtonTitle: "Cancel", startScanningAtLoad: false);
        self.view.addSubview(qrvc.view);
    }
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated);
        qrvc.view.frame = self.view.bounds;
        qrvc?.delegate = self
        qrvc?.startScanning();
    }
    
    func result(callback:ScannerCallback)
    {
        _callback = callback
    }
    
    func reader(reader: QRCodeReaderViewController, didScanResult result: QRCodeReaderResult) {
        let result = parseUrl(result.value)
        
        if _callback != nil
        {
            _callback!(result: result)
        }
        
    }

    
    func readerDidCancel(reader: QRCodeReaderViewController) {
        if _callback != nil
        {
            _callback!(result: nil)
        }
    }
    
    
    func parseUrl(url:String) -> ScannerResult?
    {
        var result: ScannerResult?;
        let expr = try! NSRegularExpression(pattern: urlPattern, options: [.CaseInsensitive])
        let matches = expr.matchesInString(url, options: [], range: NSMakeRange(0, url.characters.count))
        print("qr matches: \(matches.count)")
        if(matches.count == 1)
        {
            let match = matches[0]
            let rangeType = match.rangeAtIndex(1)
            let rangeId = match.rangeAtIndex(2)
            
            let str = url as NSString
            
            let type = str.substringWithRange(rangeType)
            let id = str.substringWithRange(rangeId)
            
            result = ScannerResult(type: type, id: id)
        }
        else{
            SVProgressHUD.setDefaultMaskType(SVProgressHUDMaskType.Black)
            SVProgressHUD.setDefaultStyle(SVProgressHUDStyle.Dark)
            SVProgressHUD.showErrorWithStatus("QR code riconosciuto :(")
            SVProgressHUD.dismissAfter(1.0)
        }
        
        return result
    }

}

In this example you can see that we always return a specific type of url and parse it. You can also just return whatever the raw result and diplay it or parse it as needed.

Now in my first controller, where I tapped the QRCode button to open the scanner, I perform a segue :

@IBAction func qrCodeTapped(sender: AnyObject) {
        self.performSegueWithIdentifier("scanner", sender: nil)
    }

And in `prepareForSegue` I just return the result of the scan, like this :

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        switch segue.identifier!
        {
        case "scanner":
            let svc = segue.destinationViewController as! ScannerVC
            svc.result({ (result) in
                self.dismissViewControllerAnimated(true, completion: nil)
                if let res = result {
                    //Do something with the qrcode result
                }
            })
            break
            
        default: break
            
        }
        
    }

Here is the result when the user tap on the qrcode button top right (you can try it out by downloading the app on the Apple Store) :

2.2. Inside a subview

class QRScannerVC: UIViewController
{
    @IBOutlet weak var cameraView: ReaderOverlayView!
    @IBOutlet weak var scanButton: UIButton!

    let codeReader: QRCodeReader = QRCodeReader()

    override func viewDidLoad() {
        
        cameraView.layer.borderColor = UIColor.whiteColor().CGColor
        cameraView.layer.borderWidth = 1
        cameraView.layer.cornerRadius = 10
        
        scanButton.layer.cornerRadius = 25
        scanButton.titleLabel!.font = UIFont(name: KYTheme.boldFontName, size: 17)
        
        //Insert the qrcode scanner camera view
        cameraView.layer.insertSublayer(codeReader.previewLayer, atIndex: 0)
        
        codeReader.completionBlock = { (result) in
            self.didScanQRCode(result)
        }
        
    }
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        codeReader.previewLayer.frame = cameraView.bounds
    }
    
    override func viewWillDisappear(animated: Bool) {
        codeReader.stopScanning()
        scanButton.hidden = false
        cameraView.hidden = true
        super.viewWillDisappear(animated)
        self.navigationController?.navigationBarHidden = false
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        //resize the previewLayer to the size of cameraView
        codeReader.previewLayer.frame = cameraView.bounds
    }
    
   
    //on tap, start scanning
    @IBAction func scanButton(sender: AnyObject) {
        scanButton.hidden = true
        codeReader.startScanning()
        cameraView.hidden = false;
        
    }
    
    func didScanQRCode(result: QRCodeReaderResult)
    {
        //Send QRCode result to server for processing
        QueryAPI.sharedInstance.sendQRCodeToServer(result.value) { (res: ApiResponse) in
            
            if(res.success) {
                self.performSegueWithIdentifier("mysegue", sender: nil)
            } else {
                //Display error and start scanning again
                self.codeReader.stopScanning()
                SVProgressHUD.setDefaultMaskType(SVProgressHUDMaskType.Black)
                SVProgressHUD.setDefaultStyle(SVProgressHUDStyle.Light)
                SVProgressHUD.showErrorWithStatus(res.message)
                SVProgressHUD.dismissAfter(2)
                self.codeReader.startScanning()
            }
        }
    }
}

This is how it looks:

 

Next: Part II – Detection with AVFoundation