【Xamarin Studio】Xamarin.FormsとXamarin.Mobileを利用した、Android, iOS両方で動く写真撮影アプリ

  • 23
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

クロスプラットフォームなカメラアプリを作る試み

Xamarin.Mobileを利用したカメラアプリを試作しました。
あまり自信はないですが、晒します。

概要

環境

詳細は以前書いたこちらの記事を参照してください。
・Mac + Xamarin Studio
・プロジェクト構成はPCL
・Xaml不使用
・iOS + Android
・XcodeとAndroidStudioは導入済み

要件

・ボタン押下 → カメラ起動 → 写真保存 → 写真表示
・なるべくコードは共通化

準備

カメラを利用するため、基本的には実機での動作確認を行います。
Androidは、端末のUSBデバッグがONであれば動くが、iOSは結構大変。。

iOSの実機デバッグ準備

下記リンクで、(元、になるのだろうか)Xamarin正規販売代理店のエクセルソフトの方が丁寧に説明してくださっています。
http://ytabuchi.hatenablog.com/entry/2015/09/18/191258
また、Qiitaにこのような記事があり、助けられました。
Xamarin StudioでiOSの無料実機テストするまでのメモ書き
デフォルトで、Xamarinでプロジェクト作成するときに、Bundle Identifierはすべて小文字に揃えられます
androidだと普通小文字ですし、それが妥当...なのでしょう...。
とにかく、app.imlで、xcodeに登録したBundle Identifierに合わせる(大文字・小文字)とうまく行きます。

コンポーネントを入れる

そんなわけで今回は、各プラットフォームのComponentsにXamarin.Mobileを突っ込みます
(NuGetパッケージにMedia Pluginというのがあり、「これを使えば共有部分だけで完結するんじゃね?」と考えていたんですが、
iOSだと落ちちゃいました。この失敗するまでの経緯もいつか記事に残そうかと思います。
うまく行っている方がいましたら、ご教示ください。。。)

  1. 各プラットフォームComponent追加画面を開く

スクリーンショット 2016-05-27 21.44.37(2).png
2. "Xamarin.Mobile"で検索
3. インストール

以上の操作をAndroidとiOSの両方で行ってください

コーディング

概要

参考にしたのは、下記のリンクです
Xamarin.Mobile
サンプルが載っているが、わかりづらい...

Camera access with Xamarin.Forms (stack overfslow)
共通UIを使いつつ、イベントを各プラットフォームで捌く方法の説明。
Xamarin.Mobileを不使用。

共有レイアウト

{プロジェクト名}.csは、下記の様にします。
(今回はCameraTest3というプロジェクト名です)

CameraTest3.cs
using System;

using Xamarin.Forms;

namespace CameraTest3
{
    public class App : Application
    {
        Button takePhotoButton = new Button() { Text = "take photo" };
        Image image = new Image();

        public App()
        {
            takePhotoButton.Command = new Command(o => takePic());
            var content = new ContentPage
            {
                Title = "CameraTest3",
                Content = new StackLayout
                {
                    VerticalOptions = LayoutOptions.Center,
                    Children = {
                        takePhotoButton,
                        image,
                    }
                }
            };
            MainPage = new NavigationPage(content);
        }

        // イベントアクション
        public event Action takePic = () => { };

        // 画像表示
        public void showImage(String filePath)
        {
            image.Source = ImageSource.FromFile(filePath);
        }
    }
}

ポイント

・置くのはButtonとImage
・ButtonインスタンスにCommandを設定
・CommandのアクションはtakePicとしている部分(空)
・画像表示のメソッドを用意

Android

パーミッション追加

AndroidManifestの"WriteExternalStorage"と"Camera"にチェックを入れる

スクリーンショット 2016-05-27 22.10.05.png

MainActivity.cs

アクティビティ

MainActivity.cs
sing System;

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Xamarin.Media;

namespace CameraTest3.Droid
{
    [Activity(Label = "CameraTest3.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        App app;
        protected override void OnCreate(Bundle bundle)
        {
            var picker = new MediaPicker(this); // Pickerインスタンス化
            if (!picker.IsCameraAvailable) Console.WriteLine("No Camera Available."); // カメラ使えるか確認
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle); // Appをインスタンス化する前にForms初期化しないと落ちる

            app = new App(); // Appインスタンス化
                        // コマンドにアクション追加
            app.takePic += () =>
            {
                var intent = picker.GetTakePhotoUI(new StoreCameraMediaOptions
                {
                    Name = "test.jpg",
                    Directory = "Sample"
                });
                StartActivityForResult(intent, 1); // 撮影完了後OnActivityResultが呼ばれる
            };

            LoadApplication(app);
        }

        protected override async void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            if (resultCode == Result.Canceled) return;
            MediaFile file = await data.GetMediaFileExtraAsync(this);
            Console.WriteLine(file.Path);
            app.showImage(file.Path);
        }
    }
}

ポイント

・Xamarin.Mediaをusingし、MediaPickerインスタンスを作成。
・MediaPickerで、カメラが利用可能か確認。
global::Xamarin.Forms.Forms.Init(this, bundle);は、appをインスタンス化する前に呼ぶ
・Appをインスタンス化
・カメラのUI(というか、intent)を呼び出すアクションを、AppのCommandに登録
・撮影結果を受け取るリスナー(OnActivityResult)で、画像を保存し、画像表示メソッド呼び出し
・Xamarin.Mobileサンプルの下の方に近い。(上の方は動かない)

iOS

AppDelegate.cs

AppDelegate.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Xamarin.Media;

using Foundation;
using UIKit;

namespace CameraTest3.iOS
{
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {
        MediaFile file;
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init(); // やはり先に初期化
            App ap = new App();
            var picker = new MediaPicker();

            ap.takePic += async () => // 今回のアクションは非同期(async)
            {
                if (!picker.IsCameraAvailable) Console.WriteLine("No camera available"); // カメラ使えるか確認(シミュレータだとここで引っかかる)
                else {
                    try
                    {
                        file = await picker.TakePhotoAsync(new StoreCameraMediaOptions // 撮影をawait
                        {
                            Name = "test.jpg",
                            Directory = "Sample",
                        });
                        Console.WriteLine(file.Path);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.StackTrace);
                    }
                    if (file != null) {
                        ap.showImage(file.Path); // 画像表示
                    } 
                }
            };

            LoadApplication(ap);

            return base.FinishedLaunching(app, options);
        }
    }
}

ポイント

・delegateに処理を書く
・takePicコマンドに追加するアクションは非同期
・Picker利用はAndroidと同様だが、利用するメソッドはTakePhotoAsync