Capture Helper for Swift

Capture Helper is the easiest and recommended way for adding the barcode scanning capabilities to a Swift application.

The Socket Mobile Capture SDK uses CocoaPods.org as dependency manager. It is very simple to add Capture to your application by adding this line in the file named Podfile at the root of your application directory:

target 'myApp'
  pod 'SKTCapture', '~> 1.3'

Once this is done, run the command pod install from the root of your project.

Note

This command will create a workspace file for the project. The project file should be closed and the workspace file should be used instead of the project file.

For more information about Cocoapods, please check: https://guides.cocoapods.org/

Capture Helper Initialization

The first thing is to import Capture in the code: #import SKTCapture

The Capture Helper can be opened by passing in argument the completion handler that is called with the result code when the open has completed.

In order for the application to receive the Capture notifications such as Device Arrival, Device Removal, Decoded Data, the reference to an object implementing such Capture Helper delegates must be passed in to the Capture Helper preferably before opening Capture Helper.

Following is an illustration of such code:

override func viewDidLoad() {
  super.viewDidLoad()
  // Do any additional setup after loading the view, typically from a nib.

  // to make all the capture helper delegates and completion handlers able to
  // update the UI without the app having to dispatch the UI update code,
  // set the dispatchQueue property to the DispatchQueue.main
  CaptureHelper.sharedInstance.dispatchQueue = DispatchQueue.main

  // there is a stack of delegates the last push is the
  // delegate active, when a new view requiring notifications from the
  // scanner, then push its delegate and pop its delegate when the
  // view is done
  CaptureHelper.sharedInstance.pushDelegate(self)

  let appInfo = SKTAppInfo();
  appInfo.developerID = "ef62bd21-59f0-4e86-82b5-546701a7ac76"
  appInfo.appID = "ios:com.socketmobile.MyTestApp"
  appInfo.appKey = "MCwCFEZvj3UrPIAnionhcucJOx7449j2AhRhBY/I5wnao/txxarXAsdtG3iKsA=="
  CaptureHelper.sharedInstance.openWithAppInfo(appInfo, withCompletionHandler: { (result) in
      print("Result of Capture initialization: \(result.rawValue)")
  })

}

Capture Helper has a shared instance that can be used across the View Controllers of an application without the need to pass a reference from one view to another.

The reference to the application implementing the Capture Helper delegate corresponding to the application needs must be passed using the Capture Helper pushDelegate method. Capture Helper provides a set of delegates that can be chosen from in order to implement only the delegates required by the application. Here is a brief descriptions of these protocols:

Capture Helper Delegates

CaptureHelperDelegate

This is the base of all the protocols used in Capture Helper. It actually does not have any delegate methods and can be used in case the application or a particular view does not want to receive any notification from Capture.

CaptureHelperDevicePresenceDelegate

This protocol defines the didNotifyArrivalForDevice and didNotifyRemovalForDevice delegates for the device arrival and device removal respectively. The device arrival occurs when a scanner is connected to the host. The device removal occurs when the scanner disconnects from the host.

CaptureHelperDeviceManagerPresenceDelegate

This protocol defines the didNotifyArrivalForDeviceManager and didNotifyRemovalForDeviceManager delegates for the device manager arrival and device manager removal respectively. The device manager arrival occurs when the device manager is ready to be used by the application.

Please refer to Using Contactless Reader/Writer with Capture Helper for more details on Device Manager.

CaptureHelperDeviceManagerDiscoveryDelegate

This protocol defines the didDiscoverDevice and didEndDiscoveryWithResult delegates occurring during a device manager device discovery operation.

Please refer to Contactless Reader/Writer Discovery for more details about Device Manager Discovery.

CaptureHelperDeviceDecodedDataDelegate

This protocol defines the didReceiveDecodedData delegate that Capture calls each time a scanner decodes a barcode or a contactless reader/writer decodes a NFC/RFID tag. This delegate is called with the decoded data and the information related to the barcode such as the barcode symbology or the Tag such as Tag type.

CaptureHelperErrorDelegate

This protocol defines the didReceiveError delegate that might be called by Capture when an unexpected error occurs.

CaptureHelperDevicePowerDelegate

This protocol defines the didChangePowerState and didChangeBatteryLevel delegates regarding the power state and the battery level. Both can be received from the scanner if it is configured to send these notifications.

CaptureHelperDeviceButtonsDelegate

This protocol defines the didChangeButtonsState delegate that occurs each time a button state changes and if the scanner has been configured to send such event.

CaptureHelperAllDelegate

This convenience protocol regroups all the previous protocols. Having a class deriving from this protocol requires to define each of the delegates mentioned above.

Here is a typical implementation of the delegates:

class MasterViewController:
  UITableViewController,
  CaptureHelperDevicePresenceDelegate,
  CaptureHelperDeviceDecodedDataDelegate,
  CaptureHelperErrorDelegate,
  CaptureHelperDevicePowerDelegate {

  ...

  func didNotifyArrivalForDevice(_ device: CaptureHelperDevice, withResult result: SKTResult) {
      print("Main view device arrival:\(String(describing: device.deviceInfo.name))")

  }

  func didNotifyRemovalForDevice(_ device: CaptureHelperDevice, withResult result: SKTResult) {
      print("Main view device removal:\(device.deviceInfo.name!)")
  }

  func didReceiveDecodedData(_ decodedData: SKTCaptureDecodedData?, fromDevice device: CaptureHelperDevice, withResult result: SKTResult) {

      if result == SKTCaptureErrors.E_NOERROR {
          let str = decodedData?.stringFromDecodedData()!
          print("Decoded Data \(String(describing: str))")
          decodedData.text = str
      }
  }

  func didReceiveError(_ error: SKTResult) {
      print("Receive a Capture error: \(error)")
  }
}

Here is a typical Capture Helper initialization:

import UIKit
import SKTCapture

class MasterViewController:
    UIViewController,
    CaptureHelperDevicePresenceDelegate,
    CaptureHelperDeviceDecodedDataDelegate,
    CaptureHelperErrorDelegate,
    CaptureHelperDevicePowerDelegate {

    // Capture Helper shareInstance allows to share
    // the same instance of Capture Helper with the
    // entire application. That static property can
    // be used in any views but it is recommended
    // to open only once Capture Helper (in the main
    // view controller) and pushDelegate, popDelegate
    // each time a new view requiring scanning capability
    // is loaded or unloaded respectively.
    var captureHelper = CaptureHelper.sharedInstance

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // there is a stack of delegates the last push is the
        // delegate active, when a new view requiring notifications from the
        // scanner, then push its delegate and pop its delegate when the
        // view is done
        captureHelper.pushDelegate(self)

        // to make all the capture helper delegates and completion handlers able to
        // update the UI without the app having to dispatch the UI update code,
        // set the dispatchQueue property to the DispatchQueue.main
        captureHelper.dispatchQueue = DispatchQueue.main

        // open Capture Helper only once in the application
        let appInfo = SKTAppInfo();
        appInfo.developerID = "ef62bd21-59f0-4e86-82b5-546701a7ac76"
        appInfo.appID = "ios:com.socketmobile.MyTestApp"
        appInfo.appKey = "MCwCFEZvj3UrPIAnionhcucJOx7449j2AhRhBY/I5wnao/txxarXAsdtG3iKsA=="
        captureHelper.openWithAppInfo(appInfo, withCompletionHandler: { (result) in
            print("Result of Capture initialization: \(result.rawValue)")
        })

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - CaptureHelperDevicePresenceDelegate

    func didNotifyArrivalForDevice(_ device: CaptureHelperDevice, withResult result: SKTResult) {
        print("Main view device arrival:\(String(describing: device.deviceInfo.name))")

    }

    func didNotifyRemovalForDevice(_ device: CaptureHelperDevice, withResult result: SKTResult) {
        print("Main view device removal:\(device.deviceInfo.name!)")
    }

    // MARK: - CaptureHelperDeviceDecodedDataDelegate

    // This delegate is called each time a decoded data is read from the scanner
    // It has a result field that should be checked before using the decoded
    // data.
    // It would be set to SKTCaptureErrors.E_CANCEL if the user taps on the
    // cancel button in the SoftScan View Finder
    func didReceiveDecodedData(_ decodedData: SKTCaptureDecodedData?, fromDevice device: CaptureHelperDevice, withResult result: SKTResult) {

        if result == SKTCaptureErrors.E_NOERROR {
            let rawData = decodedData?.decodedData
            let rawDataSize = rawData?.count
            print("Size: \(String(describing: rawDataSize))")
            print("data: \(String(describing: decodedData?.decodedData))")
            let string = decodedData?.stringFromDecodedData()!
            print("Decoded Data \(String(describing: string))")
        }
    }

    // MARK: - CaptureHelperErrorDelegate

    func didReceiveError(_ error: SKTResult) {
        print("Receive a Capture error: \(error)")
    }
}

Note

The line captureHelper.dispatchQueue = DispatchQueue.main allows the Capture Helper delegates and completion handlers to update the application UI controls directly. Setting dispatchQueue is optional, if none of the event handlers need to update the UI controls then this line can be removed.

From that moment on, if a Socket cordless scanner or Contactless reader/writer connects to the host, the application didNotifyArrivalForDevice delegate is invoked, and when the user then scans a barcode or tag the didReceiveDecodedData delegate is invoked with the decoded data.

Minimal Implementation

Assuming the only thing that matters to the application is to receive the decoded data, a minimal implementation could be as simple as:

import UIKit
import SKTCapture

class MasterViewController:
    UIViewController,
    CaptureHelperDeviceDecodedDataDelegate {

    @IBOutlet weak var inputBarcode: UITextField!

    // Capture Helper shareInstance allows to share
    // the same instance of Capture Helper with the
    // entire application. That static property can
    // be used in any views but it is recommended
    // to open only once Capture Helper (in the main
    // view controller) and pushDelegate, popDelegate
    // each time a new view requiring scanning capability
    // is loaded or unloaded respectively.
    var captureHelper = CaptureHelper.sharedInstance

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // there is a stack of delegates the last push is the
        // delegate active, when a new view requiring notifications from the
        // scanner, then push its delegate and pop its delegate when the
        // view is done
        captureHelper.pushDelegate(self)

        // setting the dispatchQueue to the dispatch main queue allows the
        // Capture Helper delegates and completion handlers to update directly
        // the application UI controls.
        captureHelper.dispatchQueue = DispatchQueue.main

        // open Capture Helper only once in the application
        let appInfo = SKTAppInfo();
        appInfo.developerID = "ef62bd21-59f0-4e86-82b5-546701a7ac76"
        appInfo.appID = "ios:com.socketmobile.MyTestApp"
        appInfo.appKey = "MCwCFEZvj3UrPIAnionhcucJOx7449j2AhRhBY/I5wnao/txxarXAsdtG3iKsA=="
        captureHelper.openWithAppInfo(appInfo, withCompletionHandler: { (result) in
            print("Result of Capture initialization: \(result.rawValue)")
        })

    }

    // MARK: - CaptureHelperDeviceDecodedDataDelegate

    // This delegate is called each time a decoded data is read from the scanner
    // It has a result field that should be checked before using the decoded
    // data.
    // It would be set to SKTCaptureErrors.E_CANCEL if the user taps on the
    // cancel button in the SoftScan View Finder
    func didReceiveDecodedData(_ decodedData: SKTCaptureDecodedData?, fromDevice device: CaptureHelperDevice, withResult result: SKTResult) {

        if result == SKTCaptureErrors.E_NOERROR {
            let rawData = decodedData?.decodedData
            let rawDataSize = rawData?.count
            print("Size: \(String(describing: rawDataSize))")
            print("data: \(String(describing: decodedData?.decodedData))")
            let string = decodedData?.stringFromDecodedData()!
            print("Decoded Data \(String(describing: string))")

            // here we can update the UI directly because we set
            // the deleteDispatchQueue Capture Helper property to DispatchQueue.main
            inputBarcode.text = string
        }
    }
}

Of course this sample won’t help the user if for some reason the scanner is not connected to the host, or if there is an error occurring while using Capture. For these reasons we highly recommend to track the device presence, to handle the error event and to collect the Capture version.

Note

The line captureHelper.dispatchQueue = DispatchQueue.main allows the application UI controls to be directly updated by the Capture Helper delegates and completion handlers. If none of the event handlers update the UI controls then this line can be removed.

Device Connection Awareness

This is a great way to confirm if the scanner is correctly connected to the application or even to drive the UI when the scanner connects or scans. Capture Helper automatically opens the device as soon as it receives its connection notification. Once the device is open, Capture Helper fires the device arrival delegate with a CaptureHelperDevice instance to represent this particular device. That CaptureHelperDevice instance is used at each event related to the device and can be used to retrieve or configure the device settings.

For an application to be aware of Device connections, there are 2 delegates that can be implemented: didNotifyArrivalForDevice and didNotifyRemovalForDevice.

These delegates work for any type of Socket Mobile devices, including the contactless reader/writer D600. Please refer to Presence of a Contactless Reader/Writer

Here is a sample code showing an implementation for these delegates along with an implementation for the didReceiveDecodedData delegate. The device name is added to a status text box in this particular case:

import UIKit
import SKTCapture

class DetailViewController:
  UIViewController,
  CaptureHelperDevicePresenceDelegate,
  CaptureHelperDeviceDecodedDataDelegate {
    let noScannerConnected = "No scanner connected"
    var scanners : [String] = []  // keep a list of scanners to display in the status
    var captureHelper = CaptureHelper.sharedInstance
    @IBOutlet weak var connectionStatus: UILabel!
    @IBOutlet weak var decodedData: UITextField!

    var detailItem: AnyObject?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // there is a stack of delegates the last push is the
        // delegate active, when a new view requiring notifications from the
        // scanner, then push its delegate and pop its delegate when the
        // view is done
        captureHelper.pushDelegate(self)

        // to make all the delegates able to update the UI without the app
        // having to dispatch the UI update code, set the dispatchQueue
        // property to the DispatchQueue.main
        captureHelper.dispatchQueue = DispatchQueue.main

        // open Capture Helper only once in the application
        let appInfo = SKTAppInfo();
        appInfo.developerID = "ef62bd21-59f0-4e86-82b5-546701a7ac76"
        appInfo.appID = "ios:com.socketmobile.MyTestApp"
        appInfo.appKey = "MCwCFEZvj3UrPIAnionhcucJOx7449j2AhRhBY/I5wnao/txxarXAsdtG3iKsA=="
        captureHelper.openWithAppInfo(appInfo, withCompletionHandler: { (result) in
            print("Result of Capture initialization: \(result.rawValue)")
        })
        displayScanners()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Utility functions
    func displayScanners(){
        if let status = connectionStatus {
            // the main dispatch queue is required to update the UI
            // or the dispatchQueue CaptureHelper property
            // can be set instead
            status.text = ""
            for scanner in self.scanners {
                status.text = status.text! + (scanner as String) + "\n"
            }
            if(self.scanners.count == 0){
                status.text = self.noScannerConnected
            }
        }
    }

    func displayAlertForResult(_ result: SKTResult, forOperation operation: String){
        if result != SKTCaptureErrors.E_NOERROR {
            let errorTxt = "Error \(result) while doing a \(operation)"
            let alert = UIAlertController(title: "Capture Error", message: errorTxt, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }


    // MARK: - CaptureHelperDeviceDecodedDataDelegate

    func didReceiveDecodedData(_ decodedData: SKTCaptureDecodedData?, fromDevice device: CaptureHelperDevice, withResult result:SKTResult) {
        print("didReceiveDecodedData in the detail view with result: \(result)")
        if result == SKTCaptureErrors.E_NOERROR {
            if let str = decodedData!.stringFromDecodedData() {
                print("Decoded Data \(String(describing: str))")
                self.decodedData.text = str
            }
        }
    }

    // MARK: - CaptureHelperDevicePresenceDelegate

    // since we use CaptureHelper in shared mode, we receive a device Arrival
    // each time this view becomes active and there is a scanner connected
    func didNotifyArrivalForDevice(_ device: CaptureHelperDevice, withResult result:SKTResult) {
        print("didNotifyArrivalForDevice in the detail view")
        scanners.append(device.deviceInfo.name!)
        displayScanners()
    }

    func didNotifyRemovalForDevice(_ device: CaptureHelperDevice, withResult result: SKTResult) {
        print("didNotifyRemovalForDevice in the detail view")
        var newScanners : [String] = []
        for scanner in scanners{
            if(scanner as String != device.deviceInfo.name){
                newScanners.append(scanner as String)
            }
        }
        scanners = newScanners
        displayScanners()
    }
}

Note

The line captureHelper.dispatchQueue = DispatchQueue.main allows the application UI controls to be directly updated from the Capture Helper delegates and completion handlers . If none of the event handlers updates the UI then this line can be removed.

Configuring Capture to Handle Contactless Reader/Writer Connection (D600)

Please refer to Starting Contactless Reader/Writer Auto Discovery Mode.

Adding Features to Capture Helper

Capture Helper offer the most common features. It is possible that an application requires a particular feature that is not provided by default by Capture Helper. In this case, the right approach is to extend the Capture Helper class to add the specific feature. The simplest way for doing this is to copy paste a similar feature from the actual Capture Helper source file, and create a Capture Helper or Capture Helper Device extension class by adding a new source file in project and paste and modify the code that has been previously copied. The benefit of that approach is to keep the modified code even after an update of the Capture Cocoapods. It makes a clean separation between the code added and the original code.

Following is a sample code of such CaptureHelperDevice extension:

import Foundation
import SKTCapture

extension CaptureHelper
{
    // this makes the scanner vibrate if vibrate mode is supported by the scanner
    func setDataConfirmationOkDevice(_ device: CaptureHelperDevice, withCompletionHandler completion: @escaping(_ result: SKTResult)->Void){
        // the Capture API requires to create a property object
        // that should be initialized accordingly to the property ID
        // that we are trying to set (or get)
        let property = SKTCaptureProperty()
        property.id = .dataConfirmationDevice
        property.type = .ulong
        property.uLongValue = UInt(SKTHelper.getDataComfirmation(withReserve: 0, withRumble: Int(SKTCaptureDataConfirmationRumble.good.rawValue), withBeep: Int(SKTCaptureDataConfirmationBeep.good.rawValue), withLed: Int(SKTCaptureDataConfirmationLed.green.rawValue)))

        // make sure we have a valid reference to the Capture API
        if let capture = captureApi as SKTCapture! {
            capture.setProperty(property, completionHandler: {(result, propertyResult)  in
                completion(result)
            })
        } else {
            // if the Capture API is not valid often because
            // capture hasn't be opened
            completion(SKTCaptureErrors.E_INVALIDHANDLE)
        }
    }
}