Composite Scanning

Composite scanning allows you to set up a workflow where multiple items need to be scanned.

You can build a PluginComposite, add any number of ScanViewPlugins to it as children, and then pass in the composite to the initializer of a ScanView.

Plugin composites currently can not be nested.

There are two processing modes available in a PluginComposite: sequential and parallel.

  • In sequential mode, the plugin composite runs each child plugin sequentially in the order they were added. As each plugin gets a result, the next plugin is started.

  • In parallel mode, cutouts for each child plugin are initially displayed simultaneously. As a plugin gets a result, its cutout is hidden so that the user can concentrate on scanning the remaining items. For instance, you can set up a parallel scan configuration consisting of an electric meter together with a barcode.

In a parallel composite, visual feedback and cutouts for all child plugins that haven’t scanned yet are shown at the same time. It’s a good idea to customise the view configuration for each child plugin. For instance, you could adjust the cutouts so that they do not overlap each other, or, if the cutouts do overlap, adjust the appearance of the scan feedback so it is clear to the user which feedback comes from which plugin.

You could also use the ALScanViewDelegate method scanView:updatedCutoutWithPluginID:frame: to position labels near each cutout so that the user knows what to scan with each one.

ALViewPluginComposite

ALViewPluginComposite is the class representing a plugin composite. Each composite consists of:

  • a delegate object with the type ALViewPluginCompositeDelegate

  • the processing mode, parallel or sequential

  • the composite’s plugin ID

  • a list of children view plugins

  • a map of String → ScanViewPluginConfig for each child plugin

Configuring a plugin composite with JSON

A JSON configuration for a plugin composite should be structured in this way:

{
    "viewPluginCompositeConfig": {
        "id": "parallel_composite_scanning",
        "processingMode": <PROCESSING_MODE>,
        "viewPlugins": [
            {
                "viewPluginConfig": {
                    "pluginConfig": {
                        "id": "com.anyline.configs.plugin.lp",
                        "licensePlateConfig": {
                        "scanMode": "auto"
                        }
                    },
                    ...
                },
                "viewPluginConfig": {
                    ...
                },
                ...
            }
        ]
    }
}

viewPluginCompositeConfig is the name of the root node’s JSON property. PROCESSING_MODE should be sequential or parallel. id should be a valid keyword string and is a required field. viewPlugins is an array where the viewPluginConfig JSON bodies of each child plugin is listed.

To create a plugin composite with this, use the initWithJSONDictionary:error: initializer:

NSError *error;
NSString *jsonConfig = [self getJSONConfigString];
NSDictionary *configDictionary = [jsonConfig toJSONObject:&error]; // check error

ALViewPluginComposite *pluginComposite = [[ALViewPluginComposite alloc] initWithJSONDictionary:JSONDict
                                                                                         error:&error]; // check error
pluginComposite.delegate = self; // implement the ALViewPluginCompositeDelegate

Then, provide the composite with the ScanView’s initializer:

ALScanView *scanView = [[ALScanView alloc] initWithFrame:frame
                                          scanViewPlugin:pluginComposite
                                          scanViewConfig:scanViewConfig
                                                   error:&error];

Configuring a plugin composite programmatically

To create a plugin composite with code, you would have to provide the children view plugins to ALViewPluginComposite 's initializer.

NSError *error;
NSArray<ALScanViewPlugin *> *children = @[ meterScanViewPlugin, barcodeScanViewPlugin ];
ALViewPluginComposite *pluginComposite = [[ALViewPluginComposite alloc] initWithID:@"parallel-meter-barcode"
                                                                              mode:ProcessingModeParallel
                                                                          children:children
                                                                             error:&error]; // check error
pluginComposite.delegate = self; // implement the ALViewPluginCompositeDelegate

Reading the result

For both sequential and parallel plugin composites, once all the results have been scanned, the ALViewPluginCompositeDelegate method viewPluginComposite:allResultsReceived: is called on the delegate, providing a list of ALScanResult found for each child plugin, in the order in which they were scanned.

On top of viewPluginComposite:allResultsReceived:, you can still determine when each child plugin has returned a scan result by setting their scan plugin delegates.

NSArray<ALScanViewPlugin *> *children = @[ meterScanViewPlugin, barcodeScanViewPlugin ];
ALViewPluginComposite *pluginComposite = ... create the composite passing in `children`
pluginComposite.delegate = self; // implement the ALViewPluginCompositeDelegate

meterScanViewPlugin.scanPlugin.delegate = self;
barcodeScanViewPlugin.scanPlugin.delegate = self;

// implement ALScanPluginDelegate method(s)

Example JSON

Here you will see an example JSON configuration for a composite scan setup, using a license plate and VIN plugins as children running in parallel mode:

{
  "cameraConfig": {
    "captureResolution": "1080p",
    "pictureResolution": "1080p"
  },
  "flashConfig": {
    "mode": "manual_off",
    "alignment": "bottom_left"
  },
  "viewPluginCompositeConfig": {
    "id": "com.anyline.configs.plugin.parallel-lp-vin",
    "processingMode": "parallel",
    "viewPlugins": [
      {
        "viewPluginConfig": {
          "pluginConfig": {
            "id": "com.anyline.configs.plugin.lp",
            "licensePlateConfig": {
              "scanMode": "auto"
            }
          },
          "cutoutConfig": {
            "style": "rect",
            "maxWidthPercent": "85%",
            "maxHeightPercent": "100%",
            "alignment": "top_half",
            "width": 0,
            "ratioFromSize": { "width": 4, "height": 1 },
            "offset": { "x": 0, "y": 0 },
            "strokeWidth": 2,
            "cornerRadius": 10,
            "strokeColor": "8be9fd",
            "outerColor": "000000",
            "outerAlpha": 0,
            "feedbackStrokeColor": "0099FF"
          },
          "scanFeedbackConfig": {
            "style": "rect",
            "strokeWidth": 2,
            "strokeColor": "0099FF",
            "fillColor": "330099FF",
            "cornerRadius": 0,
            "beepOnResult": false,
            "vibrateOnResult": false,
            "blinkAnimationOnResult": true
          }
        }
      },
      {
        "viewPluginConfig": {
          "pluginConfig": {
            "id": "com.anyline.configs.plugin.vin",
            "vinConfig": {}
          },
          "cutoutConfig": {
            "style": "rect",
            "maxWidthPercent": "85%",
            "alignment": "top_half",
            "ratioFromSize": { "width": 62, "height": 9 },
            "offset": { "x": 0, "y": 240 },
            "outerColor": "000000",
            "outerAlpha": 0,
            "strokeWidth": 2,
            "strokeColor": "ff79c6",
            "cornerRadius": 4,
            "feedbackStrokeColor": "0099FF"
          },
          "scanFeedbackConfig": {
            "style": "animated_rect",
            "animation": "traverse_multi",
            "animationDuration": 250,
            "strokeWidth": 2,
            "strokeColor": "0099FF",
            "fillColor": "220099FF",
            "beepOnResult": false,
            "vibrateOnResult": false,
            "blinkAnimationOnResult": true
          }
        }
      }
    ]
  }
}