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
ALScanPluginConfig
configures anALScanPlugin
, -
An
ALScanViewPluginConfig
configures anALScanViewPlugin
, and -
An
ALScanViewConfig
configures anALScanView
.
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 ALScanPluginConfig
class (NOTE: this is simplified):
@interface ALScanPluginConfig : NSObject
@property (nonatomic, readonly) ALPluginConfig *pluginConfig;
@end
It owns an ALPluginConfig
which contains within it the 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:
@interface ALMeterConfig : NSObject
@property (nonatomic, assign) ALMeterConfigScanMode *scanMode;
@property (nonatomic, nullable, strong) NSNumber *minConfidence;
@end
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:
NSError *error;
// the JSON configuration string (here hardcoded, but more typically loaded from a file included with the app bundle)
NSString *pluginConfigStr = @"{\"pluginConfig\":...}";
// convert to a dictionary type
NSDictionary *pluginConfigDict = [pluginConfigStr asJSONObject];
// initialize the Scan Plugin with the config dictionary
ALScanPlugin *scanPlugin = [[ALScanPlugin alloc] initWithJSONDictionary:pluginConfigDict error:&error];
// check the error object and handle if necessary
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
to transform a string containing your JSON configuration into a suitable If deserializing the JSON string is not possible, then both functions will return nil. In addition, if using the first
version, the Please also check the |
Configs
ScanPluginConfig
A ScanPluginConfig 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.
Inside it, an ALScanPlugin
holds an ALPluginConfig
object which offer 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
},
}
From the JSON above, one can see that the ScanPluginConfig consists of a PluginConfig
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:
ALScanPlugin *scanPlugin = [[ALScanPlugin alloc] initWithJSONDictionary:configDict error:&error];
or with an ALScanPluginConfig
object (which is essentially formed using the same configDict
object):
ALScanPluginConfig *scanPluginConfig = [[ALScanPluginConfig alloc] initWithJSONDictionary:configDict error:&error];
// check error to see if you can safely proceed with the next operation
ALScanPlugin *scanPlugin = [[ALScanPlugin alloc] initWithConfig:scanPluginConfig];
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 ScanPluginConfig, a CutoutConfig, and a ScanFeedbackConfig:
-
The ScanPluginConfig, 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
}
}
To create a ScanPluginConfig, prepare an NSDictionary
from a JSON object like the one above as you would with the ALScanPluginConfig
, and then pass it as a parameter to its initializer:
ALScanViewPluginConfig *scanViewPluginConfig = [[ALScanViewPluginConfig alloc] initWithJSONDictionary:configDictionary
error:&error];
ALScanViewPlugin *scanViewPlugin = [[ALScanViewPlugin alloc] initWithConfig:scanViewPluginConfig error:&error];
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 ALScanPluginConfig
, along with
(optionally) an ALCutoutConfig
and an ALScanFeedbackConfig
:
ALScanViewPluginConfig *scanViewPluginConfig = [[ALScanViewPluginConfig alloc] initWithScanPluginConfig:scanPluginConfig
cutoutConfig:cutoutConfig
scanFeedbackConfig:scanFeedbackConfig
error:&error];
// check the error object and handle appropriately
ALScanViewPlugin *scanViewPlugin = [[ALScanViewPlugin alloc] initWithConfig:scanViewPluginConfig
error:&error];
// check the error object and handle appropriately
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 }
},
To create an ALScanViewConfig
, form it in the usual way via JSON or through its constituent config types:
ALScanViewConfig *scanViewConfig = [[ALScanViewConfig alloc] initWithJSONDictionary:JSONDict error:&error];
ALScanView *scanView = [[ALScanView alloc] initWithFrame:frame
scanViewPlugin:scanViewPlugin
scanViewConfig:scanViewConfig
error:&error];
ALScanViewConfig *scanViewConfig = [[ALScanViewConfig alloc] initWithCameraConfig:cameraConfig
flashConfig:flashConfig];
ALScanView *scanView = [[ALScanView alloc] initWithFrame:frame
scanViewPlugin:scanViewPlugin
scanViewConfig:scanViewConfig
error:&error];
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
.
ALFlashConfig *cameraConfig = [[ALCameraConfig alloc] initWithJSONDictionary:cameraJSONConfig]
ALFlashConfig *flashConfig = [[ALFlashConfig alloc] initWithJSONDictionary:flashJSONConfig]
ALScanViewConfig *scanViewConfig = [[ALScanViewConfig alloc] initWithCameraConfig:cameraConfig flashConfig:flashConfig];
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.
{
"flashConfig": {
"mode": "manual_off",
"alignment": "top_left",
"image": "my_custom_flash_image"
}
}
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 withScanPluginConfig:scanPluginConfig]
is an equivalent call to
[[ALScanViewPluginConfig alloc] initWithScanPluginConfig:scanPluginConfig error:&error]
With these methods, no NSError
reference parameter is required, but the functions may still potentially return
null values.
Having said that, we would still recommend the version that takes an NSError
parameter and to use it to handle
any problems accordingly.
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:
{
"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
}
}
}
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
NSString *JSONString = [self getJSONStringForFile:@"sample_id_config"];
// NOTE: implementation found elsewhere, simplified here
CGRect frame = [self getScanViewFrame];
NSError *error;
NSDictionary *configJSONDictionary = [JSONString toJSONObject:&error];
if (!configJSONDictionary) {
// handle the error object
}
// takes "viewPluginConfig", which covers "pluginConfig", "cutoutConfig", and "scanFeedbackConfig"
ALScanViewPlugin *scanViewPlugin = [[ALScanViewPlugin alloc] initWithJSONDictionary:configJSONDictionary error:&error];
if (!scanViewPlugin) {
if (error) {
NSLog(@"there was an error: %@", error.localizedDescription);
}
return;
}
// get the scan plugin from the scan view plugin
ALScanPlugin *scanPlugin = scanViewPlugin.scanPlugin;
scanPlugin.delegate = self;
// takes "cameraConfig" and "flashConfig" from the configJSONDictionary
ALScanViewConfig *scanViewConfig = [[ALScanViewConfig alloc] initWithJSONDictionary:configJSONDictionary error:&error];
// take scanViewPlugin, and scanViewConfig
ALScanView *scanView = [[ALScanView alloc] initWithFrame:frame
scanViewPlugin:scanViewPlugin
scanViewConfig:scanViewConfig
error:&error];
if (!scanView) {
// 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
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 afterwards 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:
// It is not shown here, but if an NSError is passed as a parameter make sure to
// check it after each call and handle appropriately.
NSError *error;
// configure the scan plugin (Dial Meter)
ALMeterConfig *meterConfig = [[ALMeterConfig alloc] init];
meterConfig.scanMode = [ALMeterConfigScanMode dialMeter];
ALPluginConfig *pluginConfig = [[ALPluginConfig alloc] init];
pluginConfig.meterConfig = meterConfig;
pluginConfig.identifier = @"Dial Meter";
pluginConfig.cancelOnResult = @(YES);
// create the scan plugin config
ALScanPluginConfig *scanPluginConfig = [[ALScanPluginConfig alloc] initWithPluginConfig:pluginConfig];
// create the cutout config
NSString *JSONString = [[ALCutoutConfig defaultCutoutConfig] asJSONString];
NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionaryWithDictionary:[JSONString asJSONObject]];
// configure the cutout config using a memberwise initializer
ALCutoutConfig *cutoutConfig = [[ALCutoutConfig alloc] initWithAlignment:ALCutoutAlignmentTopHalf
animation:ALCutoutAnimationStyleNone
ratioFromSize:CGSizeMake(3, 1)
offset:CGPointZero
width:720
maxHeightPercent:100
maxWidthPercent:100
cornerRadius:4
strokeWidth:2
strokeColor:@"0099FF"
feedbackStrokeColor:@"0099FF"
outerColor:@"30000000"
cropOffset:CGPointZero
cropPadding:CGSizeZero
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 defaultScanFeedbackConfig] asJSONString];
JSONDictionary = [NSMutableDictionary dictionaryWithDictionary:[JSONString asJSONObject]];
JSONDictionary[@"beepOnResult"] = @(YES);
JSONDictionary[@"vibrateOnResult"] = @(YES);
ALScanFeedbackConfig *scanFeedbackConfig = [ALScanFeedbackConfig withJSONDictionary:JSONDictionary];
// assemble and create the scan view plugin config
ALScanViewPluginConfig *scanViewPluginConfig = [[ALScanViewPluginConfig alloc] initWithScanPluginConfig:scanPluginConfig
cutoutConfig:cutoutConfig
scanFeedbackConfig:scanFeedbackConfig
error:&error];
// Create the scan view plugin
self.scanViewPlugin = [[ALScanViewPlugin alloc] initWithConfig:scanViewPluginConfig error:&error];
// 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
ALCameraConfig *cameraConfig = [ALCameraConfig defaultCameraConfig];
// configure and create the flash config
JSONString = [[ALFlashConfig defaultFlashConfig] asJSONString];
JSONDictionary = [NSMutableDictionary dictionaryWithDictionary:[JSONString asJSONObject]];
JSONDictionary[@"alignment"] = @"bottom_right";
ALFlashConfig *flashConfig = [ALFlashConfig withJSONDictionary:JSONDictionary];
// note that you can pass null to either config here and the config defaults would be used.
ALScanViewConfig *scanViewConfig = [[ALScanViewConfig alloc] initWithCameraConfig:cameraConfig
flashConfig:flashConfig];
self.scanView = [[ALScanView alloc] initWithFrame:CGRectZero
scanViewPlugin:scanViewPlugin
scanViewConfig:scanViewConfig
error:&error];
// Next Steps:
// - install the scan view into the view hierarchy
// - implement ALScanPluginDelegate for this view controller
// - start the scan view camera
// - start the scan view plugin
From the code example above, you can see how config objects can be created either through memberwise
initialization (see: 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:
((ALScanViewPlugin *)self.scanViewPlugin).scanPlugin.scanPluginConfig.pluginConfig.meterConfig.scanMode = ALMeterConfigScanMode.dialMeter;
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:
+ (ALScanViewPluginConfig *)defaultScanViewPluginConfig {
// here we use a JSON-based config as a starting point. We will be introducing changes to it later.
NSString *configStr = [self.class defaultJSONConfigStr];
return [[ALScanViewPluginConfig alloc] initWithJSONDictionary:[configStr asJSONObject] error:nil];
}
+ (ALScanViewConfig *)defaultScanViewConfig {
NSString *configStr = [self.class defaultJSONConfigStr];
return [[ALScanViewConfig alloc] initWithJSONDictionary:[configStr asJSONObject] error:nil];
}
+ (ALScanViewPlugin *)scanViewPluginWithMeterScanMode:(ALMeterConfigScanMode *)scanMode {
// 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.
ALScanViewPluginConfig *scanViewPluginConfig = [self.class defaultScanViewPluginConfig];
ALMeterConfig *meterConfig = scanViewPluginConfig.scanPluginConfig.pluginConfig.meterConfig;
meterConfig.scanMode = scanMode;
NSError *error;
ALScanViewPlugin *ret = [[ALScanViewPlugin alloc] initWithConfig:scanViewPluginConfig error:&error];
// check and handle any errors
return ret;
}
- (void)loadScanViewWithMeterScanMode:(ALMeterConfigScanMode *)newScanMode {
ALScanViewPlugin *newScanViewPlugin = [self.class scanViewPluginWithMeterScanMode:newScanMode];
NSError *error;
[self.scanView setScanViewPlugin:newScanViewPlugin error:&error];
// handle the error, if non-null
}
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.