Implementing Anyline

This section will guide you through the implementation process of the Anyline Scan View, including configuration and handling scan results.

Since we created a Single View App, Visual Studio automatically created a Storyboard and a View Controller for us. By default, the Storyboard is named MainStoryboard.storyboard and the View Controller is named RootViewController.

In this example, we will create an MRZ use-case where we will scan Identification Documents, and our View Controller will be called ScanViewController.

Create and set up a ScanView

With the Anyline iOS SDK, the ALScanViewFactory.WithConfigFilePath() method is the easiest point of initialization to create a whole usecase according on your JSON configuration.

Providing an Anyline Configuration to the Scan View

With the Anyline Configuration, you can define your whole use case, including scanning parameters and the look & feel of your scanning view.
The configuration parameters define the visual information presented to the user, as well as the scan settings (like the resolution, etc.) and feedback that is presented to the user.

Have a look at the Scanning Capabilities section for more specific implementation details.

You can adapt your scan view easily by adding a .json file as an asset to your project and loading it. In this example, we will create a .json file and configure it for the MRZ scanning.

Visual Studio

  • In your Resources treenode, right-click on the folder and select Add > New Item…​

  • Choose Visual C# > Text File and name it scanConfig.json

Build Action

Make sure that the build action for this file is set to BundleResource.

Now open the file and paste the code from the following example:

scanConfig.json
{
	"cameraConfig": {
		"captureResolution": "1080p",
		"zoomGesture": true
	},
	"flashConfig": {
		"mode": "manual",
		"alignment": "top_left"
	},
	"viewPluginConfig": {
		"pluginConfig": {
			"id": "ID",
			"cancelOnResult": true,
			"mrzConfig": {
				"strictMode": false,
				"cropAndTransformID": false
			}
		},
		"cutoutConfig": {
			"maxWidthPercent": "90%",
			"maxHeightPercent": "90%",
			"alignment": "center",
			"ratioFromSize": {
				"width": 50,
				"height": 31
			},
			"cropPadding": {
			},
			"outerColor": "000000",
			"outerAlpha": 0.3,
			"strokeWidth": 2,
			"strokeColor": "0099FF",
			"cornerRadius": 4,
			"feedbackStrokeColor": "0099FF"
		},
		"scanFeedbackConfig": {
			"style": "rect",
			"visualFeedbackRedrawTimeout": 100,
			"strokeWidth": 2,
			"strokeColor": "0099FF",
			"fillColor": "220099FF",
			"beepOnResult": true,
			"vibrateOnResult": true,
			"blinkAnimationOnResult": false
		}
	}
}

This JSON builds the whole MRZ scanning use-case.

ViewDidAppear()

In the ViewDidAppear() lifecycle of our ScanViewController, we will initialize Anyline and start scanning as follows:

public override void ViewDidAppear(bool animated)
{
	base.ViewDidAppear(animated);

	// initialize anyline if not already initialized
	InitializeAnyline();

	if (!initialized) return;

	NSError error = null;

	BeginInvokeOnMainThread(() =>
	{
		var success = _scanView.ScanViewPlugin.StartWithError(out error);
		if (!success)
		{
			if (error != null)
			{
				ShowAlert("Start Scanning Error", error.DebugDescription);
			}
			else
			{
				ShowAlert("Start Scanning Error", "error is null");
			}
		}
	});
}


private void InitializeAnyline()
{
	NSError error = null;

	if (initialized) return;

	try
	{
		// This is the main intialization method that will create our use case depending on the JSON configuration
		_scanView = ALScanViewFactory.WithConfigFilePath("scanConfig.json", this, out error);

		if (error != null)
		{
			throw new Exception(error.LocalizedDescription);
		}

		View.AddSubview(_scanView);

		// Pin the leading edge of the scan view to the parent view

		_scanView.TranslatesAutoresizingMaskIntoConstraints = false;

		_scanView.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor).Active = true;
		_scanView.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor).Active = true;
		_scanView.TopAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.TopAnchor).Active = true;
		_scanView.BottomAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.BottomAnchor).Active = true;

		_scanView.StartCamera();

		initialized = true;
	}
	catch (Exception e)
	{
		ShowAlert("Init Error", e.Message);
	}
}

ViewWillDisappear() and ViewDidDisappear()

It is important to free the memory and tear down Anyline correctly in order to avoid memory issues.

#region teardown
public override void ViewWillDisappear(bool animated)
{
	base.ViewWillDisappear(animated);

	initialized = false;

	if (_scanView?.ScanViewPlugin == null)
		return;

	_scanView.ScanViewPlugin.Stop();
}

public override void ViewDidDisappear(bool animated)
{
	base.ViewDidDisappear(animated);
	Dispose();
}

protected override void Dispose(bool disposing)
{
	_scanView?.RemoveFromSuperview();
	_scanView?.Dispose();
	_scanView = null;
	base.Dispose(disposing);
}
#endregion

In ViewWillDisappear(), we stop the scanning process. In ViewDidDisappear(), we will finally free all resources and dispose the ScanView.

Handling Scan Results

To handle scan results, you have to implement implement the ALScanPluginDelegate delegate (learn more on Protocols, in the Xamarin.iOS documentation).
There are two ways to implement the Delegate: Strongly and Weakly typed.

From the Xamarin iOS documentation website:

The Xamarin.iOS bindings ship with a strongly typed class for every delegate protocol in iOS. However, iOS also has the concept of a weak delegate.
Instead of subclassing a class bound to the Objective-C protocol for a particular delegate, iOS also lets you choose to bind the protocol methods yourself in any class you like that derives from NSObject, decorating your methods with the ExportAttribute, and then supplying the appropriate selectors. When you take this approach, you assign an instance of your class to the WeakDelegate property instead of to the Delegate property.
A weak delegate offers you the flexibility to take your delegate class down a different inheritance hierarchy.

Here are examples on how we can implement the Anyline iOS ALScanPluginDelegate, both weak and strongly typed:

Weak

The weakly typed implementation is the one used in this tutorial, in the ViewDidAppear() example. The ScanViewController class should implement the IALScanPluginDelegate:

...

public sealed class ScanViewController : UIViewController, IALScanPluginDelegate

...

and implement the ResultReceived method of the interface as follows:

[Export("scanPlugin:resultReceived:")]
public void ResultReceived(ALScanPlugin scanPlugin, ALScanResult scanResult)
{
	System.Diagnostics.Debug.WriteLine(scanResult.AsJSONString());
}

This approach allows you to receive results, while also extending some other parent that’s not the Delegate itself.

Strong

  • First, create a class that extends the ALScanPluginDelegate delegate and implements the ResultReceived method.

    • The interface can be implemented by right-clicking on the Interface and selecting Implement Interface > Implement Interface.

  • Visual Studio automatically generates the ResultReceived() method of the interface. This method will be called everytime a result is found.

/// <summary>
/// This is the delegate class that implements the ResulReceived callback for the ScanPlugin
/// </summary>
public sealed class ScanResultDelegate : ALScanPluginDelegate
{
	public override void ResultReceived(ALScanPlugin scanPlugin, ALScanResult scanResult)
	{
		System.Diagnostics.Debug.WriteLine(scanResult.AsJSONString());
	}
}
  • Now, you can use this new class (ScanResultDelegate) as the argument for the ALScanViewFactory.WithConfigFilePath()method’s delegate argument , e.g.:

private void InitializeAnyline()
{
	...

	var myResultDelegate = new ScanResultDelegate();
	_scanView = ALScanViewFactory.WithConfigFilePath("scanConfig.json", myResultDelegate, out error);

	...
}

Full Source Code

You can find the full source code on GitHub.

Build your project and deploy your application onto your iOS device. Make sure you choose your storyboard and an adequate deployment target in your Info.plist (>= 12 - but not higher than your device target).

Info.plist and camera access

The camera usage has to be declared‚ with a description in the Info.plist file. Open this file in a text editor and add the following tags manually:

<key>NSCameraUsageDescription</key>
<string>The Camera permission is necessary to enable the scanning capabilities.</string>

Gotchas

  • Xamarin’s Garbage Collector may not be able to collect all garbage, which means that there might be memory leaks if there are any dependencies left in the ViewController. Therefore, make sure that all registered events are unregistered and all subviews are removed from the ViewController view and disposed!

  • Always make sure that scanning is stopped before the view appears!

Reduce Final App Size

Check now the Reduce App Size to learn how to remove unnecessary assets and reduce the final app size.