Barcode Scanner iOS Swift Tutorial iOS8

AVFoundation : Implementing Barcode Scanning in iOS8 With Swift

In iOS 7 apple introduced support for reading MachineReadable Code(Barcodes). As of today it supports these machine formats for reading. Frameworks also provide core images filters to generate these barcodes. In this post we will implement the same for iOS 8 and Swift.

  • UPCE
  • Code39
  • Code39Mod43
  • EAN13
  • EAN8
  • Code93
  • Code128
  • PDF417
  • QR
  • Aztec
  • Interleaved2of5
  • ITF14
  • DataMatrix

This is how the final example will work.

To implement barcode scanning in our app we need to have some idea about how AVFoundation works.

AVCaptureSession

AVCaptureSession is one of the key object that will help in managing the data flow from the capture stage through our input devices like camera/mic to output like a movie file. We can also provide custom presets which will control the quality/bitrate of the output.

AVCaptureDevice

An AVCaptureDevice object represents a physical capture device and the properties associated with that device. You use a capture device to configure the properties of the underlying hardware. A capture device also provides input data (such as audio or video) to an AVCaptureSession object. We also have the flexibility to set the properties on the input device like (focus, exposure etc) but the should be done while having a lock on that particular device object

AVCaptureInputDevice

AVCaptureInputDevice is useful for capturing the data from the input device.

AVCaptureVideoPreviewLayer

AVCaptureVideoPreviewLayer is special CGlayer which will help us display the data as captured from our input device

Here is the flow on how the start capturing input from the device.

  • Get the session object
    `let session = AVCaptureSession()`
  • Add the input and the metadataoutput object to the session
let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
var error : NSError?
let inputDevice = AVCaptureDeviceInput(device: captureDevice, error: &error)

if let inp = inputDevice {
session.addInput(inp)
} else {
println(error)
}

let output = AVCaptureMetadataOutput()
session.addOutput(output)
output.metadataObjectTypes = output.availableMetadataObjectTypes

  • Adding the preview layer to display the captured data. We will set the videoGravity to AspectFill so that it covers the full screen.
func addPreviewLayer() {
previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer?.bounds = self.view.bounds
previewLayer?.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds))
self.view.layer.addSublayer(previewLayer)
}
  •  Implementing the AVCaptureMetadataOutputObjectsDelegate delegate to be called when the barcode is detected. To get this working properly we need to call the `setMetadataObjectsDelegate` on the output device which is added to the session object . We need to have this setup before we `startRunning` the capture on the session object.
output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
session.startRunning()

The `queue` is dispatch queue on which to execute the delegate’s methods. This queue must be a serial queue to ensure that metadata objects are delivered in the order in which they were received.

  • Transfrom the coordinates . We will use the convertPoint function on the UIView to tranform the coordinates. These are the corners which were detected by the framework and we will use it to display the identified barcode.
func translatePoints(points : [AnyObject], fromView : UIView, toView: UIView) -> [CGPoint] {
    var translatedPoints : [CGPoint] = []
    for point in points {
        var dict = point as NSDictionary
        let x = CGFloat((dict.objectForKey("X") as NSNumber).floatValue)
        let y = CGFloat((dict.objectForKey("Y") as NSNumber).floatValue)
        let curr = CGPointMake(x, y)
        let currFinal = fromView.convertPoint(curr, toView: toView)
        translatedPoints.append(currFinal)
    }
    return translatedPoints
}
  • Drawing the outline around the barcode. The translatedPoints in the above function can be used to draw a bezierpath.
  • To remove the outline once the barcode moves off the screen . We setup the timer which will be called when the the barcode is detected. In that startTimer function we invalidate and remove the outline as
//
// DiscoveredBarCodeView.swift
// BarcodeInventory
//
// Created by Shrikar Archak on 1/22/15.
// Copyright (c) 2015 Shrikar Archak. All rights reserved.
//

import UIKit

class DiscoveredBarCodeView: UIView {

var borderLayer : CAShapeLayer?
var corners : [CGPoint]?
override init(frame: CGRect) {
super.init(frame: frame)
self.setMyView()
}

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

func drawBorder(points : [CGPoint]) {
self.corners = points
let path = UIBezierPath()

println(points)
path.moveToPoint(points.first!)
for (var i = 1; i < points.count; i++) {
path.addLineToPoint(points[i])
}
path.addLineToPoint(points.first!)
borderLayer?.path = path.CGPath
}

func setMyView() {
borderLayer = CAShapeLayer()
borderLayer?.strokeColor = UIColor.redColor().CGColor
borderLayer?.lineWidth = 2.0
borderLayer?.fillColor = UIColor.clearColor().CGColor
self.layer.addSublayer(borderLayer)
}

}

Here is the full code for the ViewController

//  ViewController.swift
//  BarcodeInventory
//
//  Created by Shrikar Archak on 1/20/15.
//  Copyright (c) 2015 Shrikar Archak. All rights reserved.
//

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {

    let session = AVCaptureSession()
    var previewLayer : AVCaptureVideoPreviewLayer?
    var  identifiedBorder : DiscoveredBarCodeView?
    var timer : NSTimer?
    
    /* Add the preview layer here */
    func addPreviewLayer() {
        previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
        previewLayer?.bounds = self.view.bounds
        previewLayer?.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds))
        self.view.layer.addSublayer(previewLayer)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
        var error : NSError?
        let inputDevice = AVCaptureDeviceInput(device: captureDevice, error: &amp;error)
        
        if let inp = inputDevice {
            session.addInput(inp)
        } else {
            println(error)
        }
        addPreviewLayer()

        identifiedBorder = DiscoveredBarCodeView(frame: self.view.bounds)
        identifiedBorder?.backgroundColor = UIColor.clearColor()
        identifiedBorder?.hidden = true;
        self.view.addSubview(identifiedBorder!)
        
        
        /* Check for metadata */
        let output = AVCaptureMetadataOutput()
        session.addOutput(output)
        output.metadataObjectTypes = output.availableMetadataObjectTypes
        println(output.availableMetadataObjectTypes)
        output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
        session.startRunning()
    }

    override func viewWillAppear(animated: Bool) {
        
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func viewWillDisappear(animated: Bool) {
        session.stopRunning()
    }

    func translatePoints(points : [AnyObject], fromView : UIView, toView: UIView) -&gt; [CGPoint] {
        var translatedPoints : [CGPoint] = []
        for point in points {
            var dict = point as NSDictionary
            let x = CGFloat((dict.objectForKey("X") as NSNumber).floatValue)
            let y = CGFloat((dict.objectForKey("Y") as NSNumber).floatValue)
            let curr = CGPointMake(x, y)
            let currFinal = fromView.convertPoint(curr, toView: toView)
            translatedPoints.append(currFinal)
        }
        return translatedPoints
    }
    
    func startTimer() {
        if timer?.valid != true {
            timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: "removeBorder", userInfo: nil, repeats: false)
        } else {
            timer?.invalidate()
        }
    }
    
    func removeBorder() {
        /* Remove the identified border */
        self.identifiedBorder?.hidden = true
    }
    
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
        for data in metadataObjects {
            let metaData = data as AVMetadataObject
            let transformed = previewLayer?.transformedMetadataObjectForMetadataObject(metaData) as? AVMetadataMachineReadableCodeObject
            if let unwraped = transformed {
                identifiedBorder?.frame = unwraped.bounds
                identifiedBorder?.hidden = false
                let identifiedCorners = self.translatePoints(unwraped.corners, fromView: self.view, toView: self.identifiedBorder!)
                identifiedBorder?.drawBorder(identifiedCorners)
                self.identifiedBorder?.hidden = false
                self.startTimer()
                
            }
        }
    }
}

Thanks to Sam Davies for this awesome article http://www.shinobicontrols.com/blog/posts/2013/10/11/ios7-day-by-day-day-16-decoding-qr-codes-with-avfoundation

Please let me know if you have any questions/comments.

About the author

Shrikar

Backend/Infrastructure Engineer by Day. iOS Developer for the rest of the time.

  • Harsh Shrivastava

    Great tutorial! I have one question, how can I resize the camera?

    • http://shrikar.com shrikar

      If you are talking about the preview layer you can change the frame.

  • Amanpreet Singh

    for (var i = 1; i < points.count; i++)
    this line is giving error , is that mean i <= points.count

    • http://shrikar.com shrikar

      Look like a problem with html tag should work now it shoud be i < points.count

  • Amanpreet Singh

    Where is the number of barcode stored? Is it in transformed.string?

  • Allen Wixted

    Hey everyone. Is there any way to use stored images instead of the camera itself? So say I store a QR code how can I access and read that? A point in the right direction would be great!

  • Karl Shifflett

    I’m a super new be to iOS. Do you have a project for download?

    • Karl Shifflett

      I was able to get to this work on Swift 2. I’m not sure where or how to capture the result of scanning the barcode. Any assistance would be splendid. Thank you.

  • Renato Parreira

    And now what? It only scans the barcode? It doesn’t read?

    • Renato Parreira

      Ok. I got it! Just use:

      unwraped.stringValue

      to get the string in human readable format

  • George Brown

    Hi Shrikar, implementing this code into a view controller with a modal segue, I get the following error: “Optional(Error Domain=AVFoundationErrorDomain Code=-11814 “Cannot Record” UserInfo=0x7be64b80 {NSLocalizedRecoverySuggestion=Try recording again., NSLocalizedDescription=Cannot Record})”

    Looking into the docs I saw the code -11814 was an error for the device not being connected.

    What do I do??

    • http://shrikar.com shrikar

      You can’t run this code on the simulator you need to be connected it to the device.

      • George Brown

        Alright many thanks

  • Peter Kruty

    Great tutorial, with minor changes works in final XCode 7 and iOS9 :) like a charm. Now I simply need analyze and understand how it works. Thanks!

    • http://shrikar.com shrikar

      Cool. Its kind of hard to keep up with the new version of swift and xcode as they are releasing new versions so often.

      • abhijeet srivastava

        Can you like tell me the minor changes required for ios 9? i am a new dev and facing problem while implementing it in ios9

  • Henry

    has anyone tried it with a PDF417 barcode? For some reason it should bring a lot of characters (like 700) but the stringValue only has 16 characters.

  • Vky

    Hi!its vky
    i want to create a simple app that can scan a barcode and print a UILabel e.g:(Successful or not like)
    so plz give me an appropriate link r some of Shrikar tutorial that may helps me
    thanks i will be waiting guyss

    i am creating my app on objective c that generates a 1D barcode now i
    wnat to scan that but i dont know how to scan that so help me

  • Дима D-Link

    Can anyone explain me why I am getting a white screen? Maybe i missed something?

  • Дима D-Link

    Can anyone explain me why I am getting a white screen? What I am missing?

  • http://www.barcodespider.com Mario

    Good article … very useful.

  • Armando

    Hi Shrikar, I am brand new coding but I would like to learn and make an app first time ever, I just think would be nice make an app that help me improve a process in my job, reading here and there about barcode app never get to know if these can be run by simulator or how can I know the actual barcode reading its working? please help

  • Sandeep

    It doesnt recognize the last two,five supplemental digits for EAN13 barcode. Is there a workaround for that?

/* ]]> */