Building a Barcode and a QR Code Reader Application for iOS using Swift 4: PT3

Category: Swift
Building a Barcode and a QR Code Reader Application for iOS using Swift 4: PT3

This is the third part on building barcode and QRcode reader for iOS using Swift 4. In this article we will add a layer outlining detected barcode. We will also add a sound notification using iOS built in system sound and using our custom sound file. And finally we will navigate to the new ViewController.

Adding rectangle

Let’s start adding an outlining green box around the detected barcode.
First we need to define the box itself. We will be using UIView that will have a transparent background and green border. Here’s the sample:

let codeFrame:UIView = {
        let codeFrame = UIView()
        codeFrame.layer.borderColor = UIColor.green.cgColor
        codeFrame.layer.borderWidth = 2
        codeFrame.frame = CGRect.zero
        codeFrame.translatesAutoresizingMaskIntoConstraints = false
        return codeFrame
}()

To place the box in the position of the detected Barcode we will use its frame property which takes four parameters: x and y coordinates, width and height. We will get those values from videoPreviewLayer transformed Metadata Object's bounds. transformedMetadataObject(for:) converts a metadata object’s visual properties to layer coordinates.

TParameter that we have to pass is metadataObject. It is the metadata object whose visual properties we want to convert. And in our case it’s the metadata object that we already detected and took the string value out of it. The sample looks like this:

guard let stringCodeValue = metadataObject.stringValue else { return }
        
view.addSubview(codeFrame)
        
guard let barcodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObject) else { return }
   codeFrame.frame = barcodeObject.bounds
   codeLabel.text = stringCodeValue

If you take a look at the code that we previously wrote you will notice that we set codeFrame .frame parameter to CGRect.zero. We did this because we didn’t want it to show up anywhere on the screen. Now that we grabbed the coordinates and the size of the detected object we set coordinates and dimensions accordingly for our rectangle.

That’s it! Couple of lines of code and now we are able to outline the detected object. Whole code inside metadataOutput function looks like this:

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if metadataObjects.count == 0 {
            //print("No Input Detected")
            codeFrame.frame = CGRect.zero
            codeLabel.text = "No Data"
            return
        }
        
        let metadataObject = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
        
        guard let stringCodeValue = metadataObject.stringValue else { return }
        
        view.addSubview(codeFrame)
        
        guard let barcodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObject) else { return }
        codeFrame.frame = barcodeObject.bounds
        codeLabel.text = stringCodeValue
        
        // Play system sound with custom mp3 file
        if let customSoundUrl = Bundle.main.url(forResource: "beep-07", withExtension: "mp3") {
            var customSoundId: SystemSoundID = 0
            AudioServicesCreateSystemSoundID(customSoundUrl as CFURL, &customSoundId)
            //let systemSoundId: SystemSoundID = 1016  // to play apple's built in sound, no need for upper 3 lines
            
            AudioServicesAddSystemSoundCompletion(customSoundId, nil, nil, { (customSoundId, _) -> Void in
                AudioServicesDisposeSystemSoundID(customSoundId)
            }, nil)
            
            AudioServicesPlaySystemSound(customSoundId)
        }
        
}

Playing notification sound

Now it’s time to play some sound as soon as the the code is detected. In this section we will first play Apple’s built in system sound and then we’ll add our custom sound. In my case it will be just a beep sound.

There have been several ways to approach this case - some use AVFoundation and some use AudioToolbox. Well we could use AVFoundation but we only play a very short sound, we don’t need to control it. You might also want to make the device to vibrate. All of this can be simply done by AudioToolbox. All of the code goes into metadataOutput function, right after we display the outlining rectangle.

First you need to import AudioToolbox. We need AudioServicesPlaySystemSound function that plays a short sound (30 seconds or less in duration). Because sound might play for several seconds, this function is executed asynchronously. To know when a sound has finished playing, call the AudioServicesAddSystemSoundCompletion(_:_:_:_:_:) function to register a callback function. Please take a look at the docs for more detailed information.

Apple has assigned numbers to specific notification sounds in the system. Here you can find the list of all system sounds available in iOS: iPhoneDevWiki or iOSSystemSoundsLibrary.

The code below displays how to play Apple’s built in system sound:

let systemSoundId: SystemSoundID = 1016
AudioServicesPlayAlertSound(customSoundId)

With the first line we assign systemSoundId a number from the list of available sounds and then we play the sound.

NOTE: You need to note that there are two functions that can play system sound. Depending on the particular iOS device, AudioServicesPlayAlertSound function plays a short sound and may invoke vibration. AudioServicesPlaySystemSound will only play system sound without vibration.

Now it’s time to load our custom sound. Simply drag and drop the file into Project Navigator window and mark copy if needed. In my case I created a folder assets and dropped the file in it.

Beep.mp3 added to Xcode 9

In order to play our sound we first need to create it by using AudioServicesCreateSystemSoundID(_:_:) function. It creates a system sound object. We need to pass two parameters inFileURL that will take the location of our file and outSystemSoundID is the number we have to assign to our sound.

Apple docs say that we need to dispose a system sound object and associated resources after we finished playing it and create again next time. We need to use AudioServicesDisposeSystemSoundID function and pass the ID of system sound object to dispose.

In order to find out when playing the sound has finished we are going to use AudioServicesAddSystemSoundCompletion function. It registers a callback function that is invoked when a specified system sound finishes playing. Take a look at documentation for this function to get a better understanding of the parameters that are being passed.

The completion callback function is a closure. It takes two parameters. We need only one parameter SystemSoundID to pass the ID to AudioServicesDisposeSystemSoundID. We don’t need the other parameter. Hence we place underscore _.

The other use case for this function might be: Because a system sound may play for several seconds, you might want to know when it has finished playing. For example, you may want to wait until a system sound has finished playing before you play another sound.

The whole code for playing system sound looks like this:

// Play system sound with custom mp3 file
if let customSoundUrl = Bundle.main.url(forResource: "beep-07", withExtension: "mp3") {
   var customSoundId: SystemSoundID = 0
   AudioServicesCreateSystemSoundID(customSoundUrl as CFURL, &customSoundId)
   //let systemSoundId: SystemSoundID = 1016  // to play apple's built in sound, no need for upper 3 lines
            
   AudioServicesAddSystemSoundCompletion(customSoundId, nil, nil, { (customSoundId, _) -> Void in
       AudioServicesDisposeSystemSoundID(customSoundId)
   }, nil)
            
         AudioServicesPlaySystemSound(customSoundId)
}

Now our application detects barcode, outlines it with the green box and plays an alert sound. You might have noticed that though the sound file is only two seconds long it beeps non-stop. This is happening because the capture session is still running. You might want it to stop as soon as you have detected a barcode. At the end of metadataOutput function simply add captureSession?.stopRunning().

Programmatically performing Segue

Now that you have performed all of the detection and alerts in our ScannerViewController it’s time to perform some logic that your application might have. The best way is to navigate to a new view controller where one might add a label to display the code that has been read and add a scan button to the view to trigger scanning again by going back to ScannerViewController.

I have used two methods for navigating to a new view controller. present(detailsViewController, animated: true, completion: nil) and navigationController?.pushViewController(detailsViewController, animated: true).

They both take user from one to another view controller but in two different ways. pushViewConrtoller function adds target view controller into the stack of the navigation view controller.Whereas present puts the target view controller on the top. You can simply just experiment with those two and see the result yourself.

To pass data to the next view controller you simply declare it in the new view controller as the similar type that you are expecting. In our case it’s a string so in the detailsViewController I declared var scannedCode:String? as an optional. In the ScannerViewController I added the code below. It’s pretty self explanatory:

func displayDetailsViewController(scannedCode: String) {
        let detailsViewController = DetailsViewController()
        detailsViewController.scannedCode = scannedCode
        //navigationController?.pushViewController(detailsViewController, animated: true)
        present(detailsViewController, animated: true, completion: nil)
}

At the end of metadataOutput function, right after captureSession?.stopRunning() simply add displayDetailsViewController(scannedCode: stringCodeValue). I’m sure there’s no need to explain what we have done here.

You can find the whole project on my Github. It includes DetailedViewController.

Conclusion

So that’s it! We have fully working Barcode and QRcode scanning application with sound alerts and outlining rectangle. I hope you found it helpful. In case of any questions, wishes, criticism please don’t hesitate to send me an email and follow me on Twitter. Cheers!