Xamarin.Forms+AVFoundationでEANバーコードの読み込み(実装編)

5日ぐらいあーでもない、こーでもないと四苦八苦していたので備忘録を兼ねて。
環境はVisual Studio for Macです。

1.必要な基礎知識ページ

基本的なコードは、以下のページに記載があるので、それを参照。
・Xamarinの手動カメラコントロール AV キャプチャセッションの作成
・ページの実装
・Xamarinのコアアニメーション

iOSの背面カメラを起動して、Xamarin.Forms上に表示させる場合は最低でも上2つのコード部分が必要。Xamarin.Forms上のカメラ画像に対して、読み取りエリア用の線を付け足す場合、3つ目の参照が必要。

2.iOS実機でカメラを利用する場合の事前準備

iOSプロジェクト内に配置されているInfo.plistに「プライバシー - カメラの利用状況の説明」が必要。iOSシミュレータではカメラ機能は使えません。

Xamarin-plist設定イメージ

これがないと「AVCaptureDeviceInput.FromDevice(CaptureDevice);」あたりでExceptionが発生します。

関係ないけど、説明欄、プルダウンで選べるのですが……VS for Macで操作がとてもし辛かった。スクロールしているのに途中でプルダウンが閉じるのは、ちょっと操作性が悪すぎますね。

3.バーコードの読み込み部分の実装

public double ReadSize_X { get; private set; } = 0.1;
public double ReadSize_Y { get; private set; } = 0.4;
public double ReadSize_Width { get; private set; } = 0.8;
public double ReadSize_Height { get; private set; } = 0.2;

(コード中略)

// Attach input to session
Session.AddInput(Input);

// AVCaptureMetaData
// create a new output
var output = new AVCaptureMetadataOutput();
Session.AddOutput(output);

// configure and attach to the output the session.
Queue = new DispatchQueue("ManCamQueue");
Recorder = new OutputRecorder();
output.SetDelegate(Recorder, Queue);

// MetadataObjectTypesは、Session.AddOutput後に設定可能
output.MetadataObjectTypes = AVMetadataObjectType.EAN8Code | 
    AVMetadataObjectType.EAN13Code;
output.RectOfInterest = new CGRect(
    ReadSize_Y, 
    1 - ReadSize_X - ReadSize_Width, 
    ReadSize_Height, 
    ReadSize_Width);
(コード後略)

「Xamarinの手動カメラコントロール AV キャプチャセッションの作成」のコード変更部分。
バーコードを読み込むために「AVCaptureMetadataOutput()」をoutputに。
大事なのは、Session.AddOutputを行った後にMetadataObjectTypeの設定すること。

Session.AddOutputを行うまでは、AvailableMetadataObjectTypesもnullなので、MetadataObjectTypesに値を設定しようとしても、Exceptionが発生します。

4.バーコードを認識した時のデリゲート実装

public class OutputRecorder  : AVCaptureMetadataOutputObjectsDelegate
{
    public override void DidOutputMetadataObjects(
       AVCaptureMetadataOutput _Output,
       AVMetadataObject[] _MetaObject,
       AVCaptureConnection _Connection)
    {
       foreach (var data in _MetaObject)
       {
           if (data.Type == AVMetadataObjectType.EAN13Code)
           {
               Console.WriteLine($"{data}");
           }
        }
    }
}

「AVCaptureMetadataOutputObjectsDelegate」を継承するクラスを作成し、「DidOutputMetadataObjects」をオーバーライドして実装すればOK。

ドキュメントを見ると、「AVCaptureMetadataOutputObjectsDelegate」でフォーカス当てられているのは「DidOutputMetadataObjects」のみなので(他はNSObjectからの継承と記載)、バーコードの時はシンプル。

なお、バーコード関連の設定についてはXamarin+C#ではなかったですが、以下のサイトを参照させて貰いました。このサイトが無かったら途中で諦めていました。ありがとうございます。

5.カメラ画像のXamarin.Forms表示と枠線追加

public class UICameraPreview : UIView
{
	AVCaptureVideoPreviewLayer previewLayer;
	CALayer subLayer;

    (中略)

    public override void LayoutSubviews()
    {
	    base.LayoutSubviews();

	    if (previewLayer != null)
        {
		    previewLayer.Frame = Bounds;

		    // subLayerについてここで変更
            // コンストラクタ実行中は、Bounds.Width/heightが取得できない
		    var layerSize_width = previewLayer.Bounds.Width;
		    var layerSize_height = previewLayer.Bounds.Height;

            // CAControlは、バーコードの読み込み部分の実装にあった
            // Publicプロパティを参照している
		    subLayer.Position = new CGPoint(
			    layerSize_width * CAControl.ReadSize_X,
			    layerSize_height * CAControl.ReadSize_Y);
		    subLayer.Bounds = new CGRect(
			    0F,
			    0F,
			    layerSize_width * CAControl.ReadSize_Width,
			    layerSize_height * CAControl.ReadSize_Height);
	    }
    }

    (中略)
    void Initialize()
    {
        (中略)
        previewLayer = new AVCaptureVideoPreviewLayer(CaptureSession)
        {
	        Frame = Bounds,
	        VideoGravity = AVLayerVideoGravity.ResizeAspectFill
        };
        Layer.AddSublayer(previewLayer);
        CaptureSession.StartRunning();

        subLayer = new CALayer
        {
           AnchorPoint = new CGPoint(0F, 0F),
           BorderColor = UIColor.Red.CGColor,
           BorderWidth = 5
        };
        Layer.AddSublayer(subLayer);
        (中略)
    }
}

Xamarin.Forms上に、iOSのネイティブコントロールをそのまま実装することが出来ないのでカスタムレンダラーを使います。カスタムレンダラーの実装自体は、「ページの実装」のコードが基本的に利用できます。
(ちなみに悪あがきとしてiOSプロジェクト側にXamarin.Formsのxamlを作ってUIViewを表示させようとしてみましたがだめでした。)

「previewLayer」がカメラ画像の表示。
「subLayer」がバーコード読み取りエリア用の枠線表示。

コンストラクタ実行中は「previewLayer」のサイズを取得できないので、読み取りエリア用の枠線は、「LayoutSubviews」メソッド内で実行するようにしています。(「LayoutSubviews」は、デバッグ確認すると画面表示時に2回コールされるので、Layerごとに呼び出しされているような気がするけど、実装実験のようなものなので気にしない。)

6.実行結果

実行結果は、以下の通り。

Xamarinカメラ機能実行イメージ

たいへんよくできました💮

いいなと思ったら応援しよう!