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.0' 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/ .. _capturehelperinitializationlabel: 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 :ref:`Using Contactless Reader/Writer with Capture Helper` for more details on Device Manager. .. _devicemanagerdiscoverydelegatelabel: CaptureHelperDeviceManagerDiscoveryDelegate ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This protocol defines the ``didDiscoverDevice`` and ``didEndDiscoveryWithResult`` delegates occurring during a device manager device discovery operation. Please refer to :ref:`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 :ref:`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 :ref:`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) } } }