takataka430’s blog

.NET系を中心に勉強したことのまとめを書きます

【Xamarin.Forms】QRコードの読み取りをする方法

Xamarin.FormsにはQRコードやバーコードを読み取るためのZXing.Net.Mobileというライブラリがあります。

GitHub - Redth/ZXing.Net.Mobile: Zxing Barcode Scanning Library for MonoTouch, Mono for Android, and Windows Phone

今回はこのライブラリを使用して、Xamarin.FormsでQRコードをスキャンして表示する簡単なアプリを作ってみました。

環境

Visual Studio Community 2017 for Mac
Xamarin.Forms (3.6.0.220655)
ZXing.Net.Mobile (2.4.1)
ZXing.Net.Mobile.Forms (2.4.1)

手順

準備

それぞれのプロジェクト(.Net Standard、iOSAndroid)に以下のNuGet Packageをインストールします。

  • ZXing.Net.Mobile
  • ZXing.Net.Mobile.Forms   
      

次にAndroidのMainActivity.csとiOSのAppDelegate.csにコードの追加を行います。   

MainActivity.cs

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        (省略)

        //追加
        ZXing.Net.Mobile.Forms.Android.Platform.Init();

        LoadApplication(new App());
    }

    //追加
    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
       global::ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
       base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

  
AppDelegate.cs  

[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        global::Xamarin.Forms.Forms.Init();

        //追加
        ZXing.Net.Mobile.Forms.iOS.Platform.Init();

        LoadApplication(new App());
        return base.FinishedLaunching(app, options);
    }
}

次に端末でカメラを使用するための準備をします。
  
Android:AndroidManifest.xmlの「必要なアクセス許可」の「カメラ」にチェックを入れる

iOS:info.plistを開き、以下の項目を追加する
* プロパティ:プライバシー-カメラの利用状況の説明
* 値:"カメラを利用してスキャンします"

最後にナビゲーションページを使うためにApp.xaml.csに以下の変更を行います。

public App()
{
    InitializeComponent();
    
    //ここを変更
    MainPage = new NavigationPage(new MainPage());
}

  
準備はこれでOKです。それでは画面を作っていきましょう。


画面作成と処理の追加

それではアプリを作っていきましょう。ページは次の2つだけです。

  • MainPage:中央にボタンがあって、それを押すとスキャンするページ(QRScanPage)に移動
  • QRScanPage:読み取りを行うと画面中央に読み取った値をアラートに表示する  OKボタンを押すと読み取りを再開

  
最初にMainPageです。このページはボタンを押して次のページに行くだけです。
  
MainPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:QRCodeStudy" 
             x:Class="QRCodeStudy.MainPage">
    <StackLayout>
        <Button Text="QRコード読み取り" 
                Clicked="OnQR"
                HorizontalOptions="Center" 
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

  

MainPage.xaml.cs

using System;
using Xamarin.Forms;

namespace QRCodeStudy
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        void OnQR(object sender, EventArgs e)
        {
            Navigation.PushAsync(new QRScanPage());
        }
    }
}

次に読み取りページを作っていきます。   

QRScanPage.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="QRCodeStudy.QRScanPage"
             xmlns:zxing="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms">
    <ContentPage.Content>
        <Grid>
            <zxing:ZXingScannerView x:Name="zxing"
                                    OnScanResult="Handle_OnScanResult"/>
            <zxing:ZXingDefaultOverlay />
        </Grid>
    </ContentPage.Content>
</ContentPage>

ZXingScannerViewのOnScanResultイベントで読み取った値をアラートに表示するように設定します。   

QRScanPage.xaml.cs

using Xamarin.Forms;

namespace QRCodeStudy
{
    public partial class QRScanPage : ContentPage
    {
        public QRScanPage()
        {
            InitializeComponent();
        }

        void Handle_OnScanResult(ZXing.Result result)
        {
            Device.BeginInvokeOnMainThread(async () =>
            {
                zxing.IsAnalyzing = false;  //読み取り停止
                await DisplayAlert("通知","次の値を読み取りました:" + result.Text,"OK");
                zxing.IsAnalyzing = true;   //読み取り再開
            });
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();
            zxing.IsScanning = true;
        }

        protected override void OnDisappearing()
        {
            zxing.IsScanning = false;
            base.OnDisappearing();
        }
    }
}

動きは以下のような感じです。
(手元にQRコードがなかったのでバーコードを読み取っています。QRコードも同様に読み取る事ができます。)

f:id:takataka430:20190321140723g:plain:w200

ViewModelで処理を行う

上のコードは値を読み取った時の処理をコードビハインドに書いています。これをビューモデルに書いてみましょう。
QRScanPageを書き換えて、ビューモデルを追加します。コードは以下の通りです。
  
QRScanPage.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="QRCodeStudy.QRScanPage"
             xmlns:zxing="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms"
             xmlns:local="clr-namespace:QRCodeStudy">
    <ContentPage.BindingContext>
        <local:QRScanPageViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <AbsoluteLayout>
            <Grid AbsoluteLayout.LayoutFlags="All"
                  AbsoluteLayout.LayoutBounds="0.5, 0.5, 1, 1">
                <zxing:ZXingScannerView x:Name="zxing"
                                        ScanResultCommand="{Binding OnScan}"
                                        IsAnalyzing="{Binding IsAnalyzing}"/>
                <zxing:ZXingDefaultOverlay />
            </Grid>
            <Frame AbsoluteLayout.LayoutFlags="PositionProportional"
                   AbsoluteLayout.LayoutBounds="0.5, 0.5, AutoSize, AutoSize"
                   IsVisible="{Binding FrameVisible}">
                <StackLayout>
                    <Label Text="読み取った値"
                           HorizontalTextAlignment="Center"/>
                    <Label Text="{Binding ScannedCode}"
                           HorizontalTextAlignment="Center"/>
                </StackLayout>
            </Frame>
        </AbsoluteLayout>
    </ContentPage.Content>
</ContentPage>

ZXingScannerViewはScanResultCommandというプロパティを持っているようなのでこれをビューモデルのCommandにバインドしてみます。また、ビューモデルからDisplayAlertを呼び出す方法がわからないので、Frameにスキャン結果を記述するようにします。   
  
QRScanPage.xaml.cs

using Xamarin.Forms;

namespace QRCodeStudy
{
    public partial class QRScanPage : ContentPage
    {
        public QRScanPage()
        {
            InitializeComponent();
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();
            zxing.IsScanning = true;
        }

        protected override void OnDisappearing()
        {
            zxing.IsScanning = false;
            base.OnDisappearing();
        }
    }
}

  

QRScanPageViewModel

using System.ComponentModel;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace QRCodeStudy
{
    public class QRScanPageViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public QRScanPageViewModel()
        {
            OnScan = new Command<ZXing.Result>((result)=>
            {
                Device.BeginInvokeOnMainThread( async () =>
                {
                    this.IsAnalyzing = false;  //読み取り停止
                    FrameVisible = true;       //Frameを表示
                    ScannedCode = result.Text;
                    await Task.Delay(1000);    //1秒待機
                    this.IsAnalyzing = true;   //読み取り再開
                    FrameVisible = false;      //Frameを非表示
                });
            });
        }

        public Command OnScan { get; }

        private bool isAnalyzing;
        public bool IsAnalyzing
        {
            get { return isAnalyzing; }
            set
            {
                if (isAnalyzing != value)
                {
                    isAnalyzing = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsAnalyzing)));
                }
            }
        }

        private string scannedCode;
        public string ScannedCode
        {
            get { return scannedCode; }
            set
            {
                if (scannedCode != value)
                {
                    scannedCode = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ScannedCode)));
                }
            }
        }

        private bool frameVisible;
        public bool FrameVisible
        {
            get { return frameVisible; }
            set
            {
                if (frameVisible != value)
                {
                    frameVisible = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FrameVisible)));
                }
            }
        }
    }
}

コードを読み取ったら読み取った値が記載されているFrameを1秒間表示し、その間は読み取りをしないようにします。Frameが非表示になったら読み取りを再開するようにしています。
  
動きを見てみましょう。

f:id:takataka430:20190321153800g:plain:w200

うまく動きました!

参考にしたページ

ZXing.Net Mobile を使ってみた - Android 編 - - Xamarin 日本語情報

ZXing.Net Mobile を使ってみた - iOS、UWP 編 - - Xamarin 日本語情報

GitHub - Redth/ZXing.Net.Mobile: Zxing Barcode Scanning Library for MonoTouch, Mono for Android, and Windows Phone

Is there a way to render the Zxing ScannerPage in Xaml? — Xamarin Community Forums