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シミュレータではカメラ機能は使えません。
これがないと「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.実行結果
実行結果は、以下の通り。
たいへんよくできました💮