Plugin Configuration

Anyline offers a wide range of scanning capabilities covering use cases for ID, License Plate, Barcode, Meter, and so on. A Plugin is a component of the Anyline SDK that implements a scanning use case.

It is possible to configure the scanning behavior either through a JSON configuration or via code. We’ll cover both in this section.

The Configuration object and JSON

A config object determines how a ScanPlugin, ScanViewPlugin, or ScanView appears or operates. Each of these three components can be initialized using the following config objects:

  • An ALPluginConfig configures an ALScanPlugin,

  • An ALScanViewPluginConfig configures an ALScanViewPlugin, and

  • An ALScanViewConfig configures an ALScanView.

To initialize a config object, pass a child (or children) config object corresponding to its type, or its JSON representation in an NSDictionary, as a parameter in its initializer.

For an in-depth guide on how to structure your configurations through JSON, please refer to Anyline View Configuration.

To give an example, take the ALPluginConfig class (NOTE: this is simplified):

class ALPluginConfig {
  var identifier: String
  var cancelOnResult: NSNumber?
  var startScanDelay: NSNumber?
  var barcodeConfig: ALBarcodeConfig?
  // ...
}
swift

It contains an identifier string for the plugin, as well as configuration details for all possible use cases supported by Anyline. An example of such a use case, Meter Scanning, can be traced back to a property named meterConfig under ALPluginConfig, the interface of which looks like the following:

class ALMeterConfig {
  var scanMode: ALMeterConfigScanMode
  var minConfidence: NSNumber?
}
swift

To construct an Anyline component or its config object, it is usually more convenient to prepare an NSDictionary holding the JSON configuration for your use case, and then create the Anyline component or config object using initializer methods that accept the dictionary as a parameter:

// the JSON configuration string (here hardcoded, but more typically loaded from a file included with the app bundle)
let pluginConfigStr = "{\"pluginConfig\":...}"

// convert to a dictionary type (handle if null)
let pluginConfigDict: [AnyHashable: Any]! = pluginConfigStr.asJSONObject();

// initialize the Scan Plugin with the config dictionary
do {
  let scanPlugin = try ALScanPlugin(jsonDictionary: pluginConfigDict)
} catch {
  // check the error object and handle if necessary
}
swift

Alternative methods to initialize these components are also available, for when you want additional run-time control over the configuration, or if you want to forego passing an error object. However, provided that you structure your config JSON correctly, the above example remains the simplest way to set up your Anyline scan components.

You may use the String extension methods toJSONObject and asJSONObject to transform a string containing your JSON configuration into a suitable [AnyHashable: Any] type for your Anyline class initializers.

If deserializing the JSON string is not possible, then both functions will return a null value. In addition, the first version is a throwing method which allows you to inspect the error object in case the operation failed to complete successfully.

Configs

PluginConfig

A PluginConfig determines, among other things, the scanning use case and essential characteristics of an Anyline plugin’s scanning behavior. Its main purpose is to be used to initialize an ALScanPlugin object, whose constructor can take this config object as a parameter.

An ALPluginConfig object will also include further customization possibilities for specific uses cases.

It is perhaps easier to see this config object through its JSON representation (which can also be obtained via the asJSONString method). Here we give one such representation of a scan plugin config designed to scan electric meters:

"pluginConfig": {
    "meterConfig": {
        "scanMode": "auto_analog_digital_meter",
        "id": "METER",
        "cancelOnResult": true
    },
}
json

From the JSON above, one can see that the PluginConfig consists of a property named meterConfig, which further contains specific information that it is going to be run with the auto_analog_digital_meter mode.

For additional details regarding plugin configs, check ALPluginConfig.h.

To initialize a ScanPlugin, pass a dictionary based on the JSON config object above:

do {
  let scanPlugin = try ALScanPlugin(jsonDictionary: configDict)
} catch {
  // handle the error object
}
swift

or with an ALPluginConfig object (which is essentially formed using the same configDict object):

do {
  let pluginConfig = try ALPluginConfig(jsonDictionary: configDict)
  // check error to see if you can safely proceed with the next operation
  let scanPlugin = try ALScanPlugin(config: pluginConfig)
} catch {
  // handle the error object
}
swift
As soon as a Plugin component is instantiated with its config object, you will no longer be able to change the object during runtime.

ScanViewPluginConfig

A ScanViewPluginConfig is an aggregate config structure that holds a PluginConfig, a CutoutConfig, and a ScanFeedbackConfig:

  • The PluginConfig, as discussed in the previous section, determines the use case and scanning behavior of the plugin.

  • A CutoutConfig includes details about the camera view cutout to be displayed on the scan view, including where it lies within the scan view, or how much space the cutout region occupies.

  • A ScanFeedbackConfig holds properties to help you configure how the objects of interest positioned within the camera cutout are visually treated, and what happens when a scan result is found.

An example JSON representation of a ScanViewPluginConfig is shown below for the meter use case:

"viewPluginConfig": {
    "pluginConfig": {
        "id": "METER",
        "meterConfig": {
            "scanMode": "auto_analog_digital_meter"
        },
        "cancelOnResult": true
    },
    "cutoutConfig": {
        "maxWidthPercent": "75%",
        "maxHeightPercent": "90%",
        "ratioFromSize": { "width": 7, "height": 3 },
        "cornerRadius": 8,
        "strokeColor": "#8be9fd",
        "strokeWidth": 3
    },
    "scanFeedbackConfig": {
        "style": "rect",
        "strokeWidth": 2,
        "strokeColor": "#0099FF",
        "fillColor": "#220099FF",
        "beepOnResult": true,
        "vibrateOnResult": true,
        "blinkAnimationOnResult": true
    }
}
json

To create a ScanViewPluginConfig, prepare an NSDictionary from a JSON object like the one above, and then pass it as a parameter to its initializer:

do {
  let scanViewPluginConfig = try ALScanViewPluginConfig(jsonDictionary: configDict)
  let scanViewPlugin = try ALScanViewPlugin(config: scanViewPluginConfig)
} catch {
  // handle the error object
}
swift
When creating an ALScanViewPlugin in this way, a corresponding ScanPlugin is also automatically created (based on the details found within pluginConfig node of the JSON) and becomes part of the ScanViewPlugin.

Or alternatively, you can create an ALScanViewPluginConfig object using an ALPluginConfig, along with (optionally) an ALCutoutConfig and an ALScanFeedbackConfig:

do {
  let scanViewPluginConfig = try ALScanViewPluginConfig(pluginConfig: pluginConfig,
                                                        cutoutConfig: cutoutConfig,
                                                  scanFeedbackConfig: scanFeedbackConfig)
  let scanViewPlugin = try ALScanViewPlugin(config: scanViewPluginConfig)
} catch {
  // handle any errors appropriately
}
swift

ScanViewConfig

An ALScanViewConfig contains configuration details controlling the camera and flash (torch) modules of the ScanView, both of which are also represented in Anyline by their own config classes (respectively, ALCameraConfig and ALFlashConfig).

An aggregated example of the configs in JSON form can look like the following:

"cameraConfig": {
    "captureResolution": "1080p"
},
"flashConfig": {
    "mode": "manual",
    "alignment": "bottom_left",
    "offset": { "x": 0, "y": -40 }
},
json

To create an ALScanViewConfig, form it in the usual way via JSON…​

do {
  let scanViewConfig = try ALScanViewConfig(jsonDictionary: JSONDict)
  let scanView = try ALScanView(frame: frame, scanViewPlugin: scanViewPlugin, scanViewConfig: scanViewConfig)
} catch {
  // handle errors
}
swift

or through its constituent config types:

do {
  let scanViewConfig = try ALScanViewConfig(cameraConfig: cameraConfig, flashConfig: flashConfig)
  let scanView = try ALScanView(frame: frame, scanViewPlugin: scanViewPlugin, scanViewConfig: scanViewConfig)
} catch {
  // handle errors
}
swift

CameraConfig and FlashConfig

The FlashConfig allows you to configure the torch module, which can include the visibility and location of the flash toggle button within the scan view. You can also set a custom image to be used in the torch toggle view.

The CameraConfig defines a few properties determining the camera module used. At the moment, the main property is zoomGesture, which, when true, allows pinch-to-zoom behavior for the running scan view.

For the time being, only 1080 is supported for the Camera Config property captureResolution.

It is possible to create ALCameraConfig and ALFlashConfig individually, if needed. Each of the two config objects can be used to create an ALScanViewConfig.

do {
  let cameraConfig = try ALCameraConfig(jsonDictionary: cameraJSONConfig)
  let flashConfig = try ALFlashConfig(jsonDictionary: flashJSONConfig)
  let scanViewConfig = try ALScanViewConfig(cameraConfig: cameraConfig, flashConfig: flashConfig)
} catch {
  // handle errors
}
swift

To set a custom image asset for the flash toggle, first add it to the application bundle and then indicate its name in the flashConfig.image attribute of the Anyline configuration you are going to use.

Setting a custom flash toggle image
{
  "flashConfig": {
    "mode": "manual_off",
    "alignment": "top_left",
    "image": "my_custom_flash_image"
  }
}
json
The image ideally should have dimensions of 28 x 28, and have the PNG format with transparency. Add scaled versions of the same image (@2x, @3x) as necessary.

Tips and Best Practices

Convenience class initializers

Each Anyline Config class also has a .withXXX() class initializer method that you can use in place of a regular initializer.

For instance,

ALScanViewPluginConfig.withPluginConfig(pluginConfig)
swift

is an equivalent call to

try? ALScanViewPluginConfig.init(pluginConfig: pluginConfig)
swift

With the first method, no do-catch is required, but it may still potentially return null values if there are errors initializing the scan view plugin config.

Use a single JSON file per use case

In most scenarios, you should find it most convenient to keep a single JSON file holding together the aggregate configuration for your scanning use case, with all component configs listed together, in your application bundle.

One example below (sample_id_config.json) describes a possible configuration for a Universal (Latin) ID use case:

sample_id_config.json
{
  "cameraConfig": {
    "captureResolution": "1080p"
  },
  "flashConfig": {
    "mode": "manual",
    "alignment": "top_right",
    "offset": { "x": 0, "y": 0 }
  },
  "viewPluginConfig": {
    "pluginConfig": {
      "id": "com.anyline.configs.plugin.id_mrz_dl",
      "universalIdConfig": {
        "allowedLayouts": {
          "mrz": [],
          "drivingLicense": [],
          "idFront": []
        },
        "alphabet": "latin",
        "drivingLicense": {
          "surname": {"scanOption": 0, "minConfidence": 40},
          "givenNames": {"scanOption": 0, "minConfidence": 40},
          "dateOfBirth": {"scanOption": 0, "minConfidence": 50},
          "placeOfBirth": {"scanOption": 1, "minConfidence": 50},
          "dateOfIssue": {"scanOption": 0, "minConfidence": 50},
          "dateOfExpiry": {"scanOption": 1, "minConfidence": 50},
          "authority": {"scanOption": 1, "minConfidence": 30},
          "documentNumber": {"scanOption": 0, "minConfidence": 40},
          "categories": {"scanOption": 1, "minConfidence": 30},
          "address": {"scanOption": 1}
        },
        "idFront": {
          "surname": {"scanOption": 0, "minConfidence": 60},
          "givenNames": {"scanOption": 0, "minConfidence": 60},
          "dateOfBirth": {"scanOption": 0, "minConfidence": 60},
          "placeOfBirth": {"scanOption": 1, "minConfidence": 60},
          "dateOfExpiry": {"scanOption": 1, "minConfidence": 60},
          "cardAccessNumber": {"scanOption": 1, "minConfidence": 60},
          "documentNumber": {"scanOption": 0, "minConfidence": 60},
          "nationality": {"scanOption": 1, "minConfidence": 60}
        }
      },
      "cancelOnResult": true
    },
    "cutoutConfig": {
      "animation": "fade",
      "maxWidthPercent": "90%",
      "maxHeightPercent": "90%",
      "width": 0,
      "alignment": "center",
      "ratioFromSize": { "width": 86, "height": 54 },
      "offset": { "x": 0, "y": 60 },
      "cropOffset": { "x": 0, "y": 0 },
      "cropPadding": { "x": 0, "y": 0 },
      "cornerRadius": 8,
      "strokeColor": "#8BE9FD",
      "strokeWidth": 2,
      "feedbackStrokeColor": "#0099FF",
      "outerColor": "#000000",
      "outerAlpha": 0.3
    },
    "scanFeedbackConfig": {
      "style": "animated_rect",
      "strokeWidth": 0,
      "strokeColor": "#8be9fd",
      "fillColor": "#908be9fd",
      "cornerRadius": 2,
      "beepOnResult": true,
      "vibrateOnResult": false,
      "blinkAnimationOnResult": false
    }
  }
}
json

Using this config JSON, you should be able to form the ALScanViewPlugin, ALScanPlugin and ALScanView in the following way:

// get the JSON string for the aggregate config
let JSONString: String = self.getJSONString(for: "sample_id_config")

// NOTE: implementation found elsewhere, simplified here
let frame: CGRect = self.getScanViewFrame();
do {
    let configJSONDictionary = try JSONString.toJSONObject()

    // takes "viewPluginConfig", which covers "pluginConfig", "cutoutConfig", and "scanFeedbackConfig"
    let scanViewPlugin = try ALScanViewPlugin(jsonDictionary: configJSONDictionary)

    // get the scan plugin from the scan view plugin
    let scanPlugin = scanViewPlugin.scanPlugin
    scanPlugin.delegate = self

    // takes "cameraConfig" and "flashConfig" from the configJSONDictionary
    let scanViewConfig = try ALScanViewConfig(jsonDictionary: configJSONDictionary)

    let scanView = try ALScanView(frame: frame, scanViewPlugin: scanViewPlugin, scanViewConfig: scanViewConfig)

} catch {
    // handle the error object.
}

// Next steps
// - implement the ALScanPluginDelegate
// - install the scan view into the view hierarchy
// - start the scan view camera
// - start the scan view plugin
swift

Building components programmatically

Constructing each component programmatically takes a bit more work, but certainly is still possible. Doing this offers you the possibility to dynamically adapt your scanning use case based on your application’s workflow or user input.

Of course, you can still combine the two approaches and construct components with JSON and then adjust them afterward in your code.

In the example below, we show how to build up such a use case for your app. Assume here that we’re working here inside your view controller:

func setupScanView() {
    do {
        let meterConfig = ALMeterConfig()

        // configure the scan plugin (Dial Meter)
        meterConfig.scanMode = .dialMeter()

        let pluginConfig = ALPluginConfig()
        pluginConfig.meterConfig = meterConfig
        pluginConfig.identifier = "Dial Meter"
        pluginConfig.cancelOnResult = NSNumber(booleanLiteral: true)

        // create the cutout config

        var JSONString = ALCutoutConfig.default().asJSONString()
        var JSONDictionary = JSONString.asJSONObject() ?? [:]

        let cutoutConfig = ALCutoutConfig(alignment: .topHalf,
                                          animation: .none,
                                          ratioFrom: .init(width: 3, height: 1),
                                          offset: .zero,
                                          width: 720,
                                          maxHeightPercent: 100,
                                          maxWidthPercent: 100,
                                          cornerRadius: 4,
                                          strokeWidth: 2,
                                          strokeColor: "0099FF",
                                          feedbackStrokeColor: "0099FF",
                                          outerColor: "30000000",
                                          cropOffset: .zero,
                                          cropPadding: .zero,
                                          image: nil)

        // create the scan feedback config using a JSON dictionary: when a value for a property is not set
        // some default will be used.

        JSONString = ALScanFeedbackConfig.default().asJSONString()
        JSONDictionary = JSONString.asJSONObject() ?? [:]
        JSONDictionary["beepOnResult"] = NSNumber(booleanLiteral: true)
        JSONDictionary["vibrateOnResult"] = NSNumber(booleanLiteral: true)

        let scanFeedbackConfig = ALScanFeedbackConfig.withJSONDictionary(JSONDictionary)

        // assemble and create the scan view plugin config
        let scanViewPluginConfig = try ALScanViewPluginConfig(pluginConfig: pluginConfig,
                                                              cutoutConfig: cutoutConfig,
                                                              scanFeedbackConfig: scanFeedbackConfig)

        // Create the scan view plugin
        self.scanViewPlugin = try ALScanViewPlugin(config: scanViewPluginConfig)


        // Note that you don't need to instantiate an ALScanPlugin yourself --
        // the ScanViewPlugin already creates this, and you simply access its scanPlugin property.
        self.scanViewPlugin.scanPlugin.delegate = self

        // create the camera config: no customization was done, so was left as it is
        let cameraConfig = ALCameraConfig.default()

        // configure and create the flash config
        JSONString = ALFlashConfig.default().asJSONString()
        JSONDictionary = JSONString.asJSONObject() ?? [:]

        JSONDictionary["alignment"] = "bottom_right"

        let flashConfig = ALFlashConfig.withJSONDictionary(JSONDictionary)

        // note that you can pass null to either config here and the config defaults would be used.
        let scanViewConfig = ALScanViewConfig(cameraConfig: cameraConfig, flashConfig: flashConfig)

        self.scanView = try ALScanView(frame: .zero,
                                       scanViewPlugin: scanViewPlugin,
                                       scanViewConfig: scanViewConfig)
    } catch {
        // handle errors
    }
}
swift

From the code example above, you can see how config objects can be created either through memberwise initialization (see: ALCutoutConfig) or with a JSON dictionary (for instance, ALScanFeedbackConfig). Each approach can be useful in their own way.

Memberwise initialization allows you to deterministically and completely define a config object, but it requires you to provide a value for every property in the config class.

The JSON approach shown above, on the other hand, is more flexible in allowing you to provide only non-default values for your config object, but you trade away a measure of type safety in using it.

Changing the config in real time

Your work flow may require you to update the scan config while leaving the scan view camera running. To take an example, for instance, you may have a UI switch over the scan view to toggle the cutout width-height ratio (eg 3:1, 2:1, and so on), and it was tapped by the user.

In such a case, it is important to remember that once a component has been created, it can no longer be modified. For example, changing the scan mode of a plugin is not possible:

self.scanViewPlugin.scanPlugin.pluginConfig.meterConfig?.scanMode = .dialMeter()
swift

Instead, one should recreate the ScanViewPlugin (including ScanPlugin) with the updated configuration, and then either replace the old scan view in the view hierarchy with a new one made with the above components, or (to avoid the display momentarily showing a black background in the process) reload the existing scan view using its -setScanViewPlugin:error: method.

You can use the approach discussed in Building components programmatically as a starting point. Structuring how you assemble the components would go a long way towards making this manageable.

Below is an example of how you might for instance change the meter scan mode dynamically:

class func defaultScanViewPluginConfig() -> ALScanViewPluginConfig {
    // here we use a JSON-based config as a starting point. We will be introducing changes to it later.
    let configStr: String = type(of: self).defaultJSONConfigStr();
    return try! ALScanViewPluginConfig(jsonDictionary: configStr.asJSONObject() ?? [:])
}

class func defaultScanViewConfig() -> ALScanViewConfig {
    let configStr = type(of: self).defaultJSONConfigStr();
    return try! ALScanViewConfig.init(jsonDictionary: configStr.asJSONObject() ?? [:])
}

class func scanViewPlugin(meterScanMode: ALMeterConfigScanMode) -> ALScanViewPlugin {
    // all config properties that can change during runtime are confined within this method.
    // In this case, we want to have a scan view plugin created for a different meter scan mode than
    // the one currently running, based on for instance, the selection of the user.
    // The result is an ALScanViewPlugin that can be given to a new or existing ALScanView to
    // effect the scan mode change.
    let scanViewPluginConfig = type(of: self).defaultScanViewPluginConfig()
    let meterConfig = scanViewPluginConfig.pluginConfig.meterConfig
    meterConfig?.scanMode = meterScanMode
    return try! ALScanViewPlugin(config: scanViewPluginConfig)
    // handle any errors as needed.
}

func loadScanView(withMeterScanMode newScanMode: ALMeterConfigScanMode) {
    let newScanViewPlugin = type(of: self).scanViewPlugin(meterScanMode: newScanMode)
    try! self.scanView.setScanViewPlugin(newScanViewPlugin)
    // handle any errors as needed.
}
swift

With this in place, you can now simply call -loadScanViewWithMeterScanMode: and update the scan process with the new meter mode whenever it is required.


Questions or need further assistance? Please reach out to the Anyline Support Helpdesk.