ぴーさんログ

だいたいXamarin.Formsのブログ

【Evolve 2016】Xamarin.Forms Previewerを試してみた!

Xamarin Evolve 2016のキーノートで遂に来ました!

念願のXAMLプレビューが!Xamarin.Formsに!

呼称は Xamarin.Forms Previewer だそうです。

というわけで早速試してみた様子がこちら

後でXamarin Studioを再起動したらAndroidもプレビュー出来るようになりました。(でもImage表示できてない気がする....まぁまだAplha版だし)

必要な環境はXamarin StudioのAlphaチャネル最新版とXamarin.Forms 2.3プレビュー版、そしてその前提環境としてXcodeを最新にしておく必要があるみたい。

もうちょっと詳しく

※Xamarin.Forms Previewerはまだ正式リリースされた機能ではないので、不安定だったりバギーだったりするかも知れません。その点はご留意ください。(2016/4/29現在)

Xamarin.Forms Previewerの公式情報をXamarinのブログで確認してみましょう。

Live from Evolve: Faster and Easier Mobile App Development with Xamarin.Forms

You can get the Xamarin.Forms Previewer today from the alpha updater channel in Xamarin Studio for Mac. To use the Xamarin.Forms previwer, your project will need to reference Xamarin.Forms 2.3 prerelease NuGet.

ふんふん、必要なのは AlphaチャンネルMac版Xamarin StudioとXamarin.Forms 2.3のプレビュー版ですね。Visual StudioやUWPはおいおいサポートされるでしょう。

Xamarin StudioをAlphaチャンネルへ

メニューから「Xamarin Studio > Check for Updates」を開いてAlphaチャンネルに切り替えます。

f:id:ticktack623:20160429130930j:plain (画像はすでにAlphaチャンネルのものです)

更新データをダウンロード出来たらXamarin Studioを再起動。

Xcodeが古い場合はこちらも最新版に更新しておきます。更新後に一度Xcodeを起動して関連ツールの更新も忘れずに。

Xamarin.Fromsのプロジェクトを作成

Xamarin.Fromsの新規プロジェクト(PCL)を作成します。(Sharedプロジェクトで作ると.xmalファイルを編集出来なくなって詰みました)

f:id:ticktack623:20160429131245j:plain

f:id:ticktack623:20160429131254j:plain

ここで一度ビルドしてHello Worldが動くことを確認。(iOSシミュレータを再起動する必要があるかも)

各プロジェクトにXamarin.Forms 2.3プレビューを追加

各プロジェクトの「パッケージ」をダブルクリックしてNuGetパッケージマネージャを開きます。 「Show pre-release packages」をチェックして、「xamarin.forms」と検索すると、プレビュー版のXamarin.Formsが見つかるのでこれをAdd Packageします。(PCLプロジェクトでのNuGetパッケージの追加が失敗することがありました。その場合はXamarin.Forms一旦削除してから入れ直します。)

f:id:ticktack623:20160429131344j:plain

ここでも一度ビルドしてHello Worldが動くことを確認。

XAML Previewerを試す

PCLプロジェクトに「Forms ContentPage Xaml」を追加。

f:id:ticktack623:20160429131400j:plain

XAMLファイルの変更がプレビューに反映されない時は、プロジェクトをビルドしたりxamlファイルを開き直すと解消される模様です。

f:id:ticktack623:20160428175311j:plain

Xamarin.Forms向けのMap機能拡張ライブラリを作ってます

https://github.com/P3PPP/MapExtensions

コンセプトはXamarin.Forms 2.1.0で追加されたEffectsを利用して、Mapコントロールを継承することなく機能を追加するというもの。 つまり、従来のカスタムMapにも適用可能。

NuGetにもすでに上がってます。

https://nuget.org/packages/Xam.MapExtensions.Forms.Plugin/

現状は地図のクリックイベントをハンドルする機能が使えます。(iOSAndroid、UWPに対応)

var map = new Xamarin.Forms.Maps.Map();
var clickBehavior = new MapExtensions.Forms.Plugin.ClickBehavior();
map.Behaviors.Add(clickBehavior);

var page = new ContentPage
{
    Content = map,
};

clickBehavior.MapClicked += (s, e) =>
    page.DisplayAlert("MapClickd", $"{e.Position.Latitude}, {e.Position.Longitude}", "OK");

clickBehavior.MapLongClicked += (s, e) =>
    page.DisplayAlert("MapLongClicked", $"{e.Position.Latitude}, {e.Position.Longitude}", "OK");

clickBehavior.MapClidkedCommand = new Command<MapExtensions.Forms.Plugin.MapClickedEventArgs>(e =>
    page.DisplayAlert("MapClickdCommand", $"{e.Position.Latitude}, {e.Position.Longitude}", "OK"));

clickBehavior.MapLongClidkedCommand = new Command<MapExtensions.Forms.Plugin.MapClickedEventArgs>(e =>
    page.DisplayAlert("MapLongClickedCommand", $"{e.Position.Latitude}, {e.Position.Longitude}", "OK"));

使い方:

  • Xamarin.Forms.Maps.MapMapExtensions.Forms.Plugin.ClickBehavior を追加。
  • ClickBehavior 経由でクリックイベントをハンドルします。
  • MapClickedEventArgs にクリックした地図上の座標が入っているのでよしなに(ピンを立てたり)。

次は座標のコレクションを渡したらPolylineをオーバーレイしてくれるような機能を追加する予定。

(まだ機能とかちゃんと煮詰めてないのにうっかりver. 1.0でリリースしちゃったよ...)

【Xamarin.Forms 2.2.0(プレビュー)】UWP Maps

Xamarin.Forms 2.2.0からUWPでもXamarin.Forms.Mapsがサポートされます。 (っていうか今まで無かったんだ...)

ざっくりとした使い方

他のプラットフォームと同様、 Xamarin.Forms.Forms.Init() の直後に Xamarin.FormsMaps.Init() を呼ぶ必要があります。 UWPの場合はパラメータとしてBing Maps Developer Centerで発行するAPIキーを渡す必要があるようです。

まず、Xamarin.FormsのプロジェクトテンプレートにXamarin.Forms.Maps 2.2.0のNuGetパッケージを追加します。

次に、UWPプロジェクトのApp.Xaml.csを修正。

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
  ~ 省略 ~
        Xamarin.Forms.Forms.Init(e);
        // Bing Maps Developer Centerで発行したAPIキーでXamarin.Forms.Mapsを初期化
        Xamarin.FormsMaps.Init("YOUR-API-KEY");
  ~ 省略 ~
}

さらに、共通コードプロジェクトのAppクラスを修正。

public App()
{
    MainPage = new ContentPage {
        Content = new Xamarin.Forms.Maps.Map(),
    };
}

実行結果

f:id:ticktack623:20160417225557j:plain

(全ての道はローマに通ず)

リンク

Request a maps authentication key - Windows app development

【Xamarin.Forms 2.2.0(プレビュー)】CarouselView

Xamarin.Forms 2.2.0 から CarouselView クラスが追加されます。

CarouselView は従来の CarouselPage を置き換える物で、CarouselPageは将来的に非推奨となります。

サンプル

基本的な使い方は ListView に似ています。しかし、DataTemplateの中身を CellではなくView にする必要がある点に注意が必要です。

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="XFApp20.MyPage">
    <ContentPage.Content>
        <CarouselView HorizontalOptions="Center"
                      VerticalOptions="Center"
                      HeightRequest="150"
                      WidthRequest="150"
                      ItemsSource="{Binding}">
            <!-- DataTemplateの中身はView派生クラス -->
            <CarouselView.ItemTemplate>
                <DataTemplate>
                    <StackLayout>
                        <BoxView HorizontalOptions="FillAndExpand"
                                 VerticalOptions="FillAndExpand"
                                 Color="{Binding Color}" />
                        <Label HorizontalOptions="Center"
                               Text="{Binding Text}" />
                    </StackLayout>
                </DataTemplate>
            </CarouselView.ItemTemplate>
        </CarouselView>
    </ContentPage.Content>
</ContentPage>

コードビハインド

public MyPage()
{
    InitializeComponent();

    BindingContext = new ViewModel[] {
        new ViewModel {
            Color = Color.Red,
            Text = "Red",
        },
        new ViewModel {
            Color = Color.Blue,
            Text = "Blue",
        },
        new ViewModel {
            Color = Color.Green,
            Text = "Green",
        },
        new ViewModel {
            Color = Color.Purple,
            Text = "Purple",
        },
        new ViewModel {
            Color = Color.Silver,
            Text = "Silver",
        },
        new ViewModel {
            Color = Color.Pink,
            Text = "Pink",
        },
    };
}
public class ViewModel
{
    public Color Color { get; set; }
    public string Text { get; set; }
}

ItemTemplateのセッティングをC#で書くとこのようになります。

carouselView.ItemTemplate = new DataTemplate(typeof(CustomView));

// または

carouselView.ItemTemplate = new DataTemplate(() => {
    var boxView = new BoxView(){
        VerticalOptions = LayoutOptions.FillAndExpand,
        HorizontalOptions = LayoutOptions.FillAndExpand,
    };
    boxView.SetBinding(BoxView.ColorProperty, "Color");
    var label = new Label{
        HorizontalOptions = LayoutOptions.Center,
    };
    label.SetBinding(Label.TextProperty, "Text");
    return new StackLayout {
        Children = {
            boxView,
            label,
        },
    };
});

実行結果

f:id:ticktack623:20160413091209g:plain:w250

【Xamarin.Forms】BindableProperty.Create() non-generic版のコードスニペット

以前、BindablePropertyを楽に作るコードスニペットを公開しました。

その中で使っていたBindableProperty.Create()のgeneric版がobsolete化したので、新しくnon-generic版のスニペットを公開します。

詳細な登録手順はgeneric版の記事を合わせてご参照ください。

Xamarin Studio用

Preferences > テキストエディタ > コード テンプレート から追加します。

Visual Studio

「BindableProperty.snippet」の名前で保存。 「C:\Users\ ユーザー名 \Documents\ VisualStudioのバージョン \Code Snippets\Visual C#\My Code Snippets」にファイルをコピー。

ツール > コードスニペットマネージャー から追加します。

【Xamarin.Forms 2.2.0(プレビュー)】Marginプロパティ

Xamarin.Forms 2.2.0 から View クラスに Margin プロパティが追加されます。 複雑なレイアウトが今までより平易に書けるようになりますね。

(ずっと来ないのでパフォーマンス的に厳しいのかと思ってまいした)

サンプル

public App()
{
    MainPage = new ContentPage {
        Content = new StackLayout {
            Orientation = StackOrientation.Horizontal,
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center,
            BackgroundColor = Color.Gray,
            Spacing = 0,
            Children = {
                new Label {
                    Text = "◯",
                    Margin = new Thickness(10, 10, 10, 10),
                    BackgroundColor = Color.Green,
                },
                new Label {
                    Text = "△",
                    Margin = new Thickness(10, 30, 10, 10),
                    BackgroundColor = Color.Blue,
                },
                new Label {
                    Text = "□",
                    Margin = new Thickness(20, 20, 20, 20),
                    BackgroundColor = Color.Red,
                },
            },
        }
    };
}

f:id:ticktack623:20160409090910j:plain

これはこれで思った通りにレイアウトするには少し慣れが必要かも。

Plugins for Xamarinを作ろう!

前置き

Build 2016での発表から全ての開発者がVisual StudioでXamarinを利用できるようになり、アプリのみならずクロスプラットフォームなライブラリ開発も行いやすくなりました。

という訳で今回はPlugins for XamarinのテンプレートからNuGetパッケージを作るまでの流れを解説します。

Plugins for Xamarin って何?

Xamarin.iOS, Xamarin.Android, Windows Phone などの固有機能を共通のインターフェースで利用できるようにデザインされたライブラリ群です。 代表的なものにバッテリー、ストレージ、位置情報などを利用するためのプラグインがあります。

Plugins for Xamarinを 使いこなす 方法はBuild Insiderで田淵さん(@ytabuchi)連載している記事を参照してください。

Plugins for Xamarinを使いこなすには?(ファイルシステム編) - Build Insider 他

環境準備

  • Visual Studio 2015 Update 2
  • Xamarin SDK
  • Plugin For Xamarin用のプロジェクトテンプレート
  • Windows 10推奨(UWPプロジェクトを含むので)

(筆者の環境はWindows 10 & Visual Studio Enterprise 2015 with Update 2です)

まずはVisual StudioとXamarinをインストールしてiOSAndroidアプリがビルドできる環境を整えてください。 (先駆者達の環境構築記事を見ながら頑張りましょう)

次にGitHubにあるPlugin For Xamarinのリポジトリを開きます。

xamarin/plugins: Plugins for Xamarin

「Tools to get Started」以下のリンクからプロジェクトテンプレートを入手してください。 (一応こちらにもリンクを張っておきます。)

プロジェクトを作ろう!

準備が整ったら新規プロジェクトを作成しましょう。 テンプレート名は「Plugin for Xamarin」です。("plugin"で絞り込みをかけると楽)

f:id:ticktack623:20160407233019j:plain

ソリューション名は"Battery"や"Geolocator"のような 機能名 にするのがお約束です。

続いて生成されたプロジェクトを眺めてみましょう。

f:id:ticktack623:20160407233839j:plain

プロジェクトが沢山あってびっくりしますが、以下の3種類があると押さえておけば問題ありません。

  • Plugin.機能名 (PCL)
  • Plugin.機能名.Abstractions (PCL)
  • Plugin.機能名.プラットフォーム名 * n個

Plugin.機能名.Abstractions (PCL)

インターフェースやenumを定義します。 出力は Plugin.機能名.Abstractions.dll となります。

Plugin.機能名 (PCL)

PCLプロジェクト用のダミー実装を定義します。「なんのこっちゃ?」と思うかもしれませんが、実はこれが無いとPCLプロジェクトでPluginを参照するときに困ります。 出力は Plugin.機能名.dll となります。

Plugin.機能名.プラットフォーム名

各プラットフォーム用の実装を定義します。 出力は Plugin.機能名.dll となります。

プラグインを実装しよう!

この記事ではサンプルとして、アプリケーションのバージョンを取得するプラグインを実装します。 (not assembly version)

Plugin.機能名.Abstractions (PCL) プロジェクト

Plugin.機能名.AbstractionsプロジェクトにはInterfaceやenum、Event等を定義します。

テンプレートから生成された IAwesomeFeature.cs にコードを追加します。

using System;

namespace Plugin.AwesomeFeature.Abstractions
{
  /// <summary>
  /// Interface for AwesomeFeature
  /// </summary>
  public interface IAwesomeFeature
  {
        // 追加したコード
        string GetAppVersion();
  }
}

Plugin.機能名 (PCL) プロジェクト

Plugin.機能名プロジェクトにはPCLプロジェクトから参照するためのダミー実装となります。 Cross機能名.cs だけが生成されており、プラグインをsingletonなAPIラッパーとする限りコードを変更する必要はありません。

using Plugin.AwesomeFeature.Abstractions;
using System;

namespace Plugin.AwesomeFeature
{
  /// <summary>
  /// Cross platform AwesomeFeature implemenations
  /// </summary>
  public class CrossAwesomeFeature
  {
    static Lazy<IAwesomeFeature> Implementation = new Lazy<IAwesomeFeature>(() => CreateAwesomeFeature(), System.Threading.LazyThreadSafetyMode.PublicationOnly);

    /// <summary>
    /// Current settings to use
    /// </summary>
    public static IAwesomeFeature Current
    {
      get
      {
        var ret = Implementation.Value;
        if (ret == null)
        {
          throw NotImplementedInReferenceAssembly();
        }
        return ret;
      }
    }

    static IAwesomeFeature CreateAwesomeFeature()
    {
#if PORTABLE
        return null;
#else
        return new AwesomeFeatureImplementation();
#endif
    }

    internal static Exception NotImplementedInReferenceAssembly()
    {
      return new NotImplementedException("This functionality is not implemented in the portable version of this assembly.  You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.");
    }
  }
}

Plugins for Xamarinは Plugin.機能名.Cross機能名.Current でアクセスするようデザインされており、 Currentプロパティにアクセスした際にプラットフォーム実装が無ければ例外となります。 (具体的には、対象プラットフォームにプラグイン実装が提供されていない場合、PCLプロジェクトにプラグインのNuGetパッケージを追加したのにプラットフォーム側のプロジェクトでパッケージ追加を忘れた場合)

ちなみに、各プラットフォームプロジェクトはこの Cross機能名.cs をリンク参照しています。

Plugin.機能名.プラットフォーム名 プロジェクト

今回はAndroid版にだけ機能を実装することにします。

各プラットフォームのプロジェクトには 機能名Implementation.cs ファイルが生成されているので、ここにコードを追加します。

using Plugin.AwesomeFeature.Abstractions;
using System;

namespace Plugin.AwesomeFeature
{
  /// <summary>
  /// Implementation for Feature
  /// </summary>
  public class AwesomeFeatureImplementation : IAwesomeFeature
  {
        // インターフェースの実装を追加
        public string GetAppVersion()
        {
            var context = Android.App.Application.Context;
            return context.PackageManager.GetPackageInfo(context.PackageName, 0).VersionName;
        }
    }
}

NuGetパッケージを作ろう!

ソリューションを右クリックから新規項目を追加。

「Plugins for Xamarin NuSpec」を追加、名前はソリューションと同じく機能名にします。 ("nuspec"でフィルタリングすると楽)

f:id:ticktack623:20160408141828j:plain

f:id:ticktack623:20160408142526j:plain

今回は動作確認のため、NuGet.orgに公開しないローカルなNuGetパッケージとして作成します。nuspecやNuGetパッケージの作り方に関しては、動作確認に必要な最低限の解説に留めます。(私にとってnugetは複雑で難しいので)

nuspecファイルを編集します。

  • <licenseUrl /><projectUrl/>
    • 必須項目なので架空のUrlで埋めておきます。
  • <files></files>

準備が整ったら、各プロジェクトをビルドしてパッケージマネージャコンソールで次のコマンドを実行します。

nuget pack

成功するとソリューションフォルダの直下にNuGetパッケージファイル(nupkg)が出力されます。

f:id:ticktack623:20160408145421j:plain

f:id:ticktack623:20160408145727j:plain

NuGetパッケージの作成はMacでも可能ですが、Windows版でしか使えない機能($id$等のマクロ、MS Buildに依存してる)があったりするのでWindowsで行った方が何かと便利でしょう。

NuGetパッケージを使おう!

それでは出来上がったNuGetパッケージをローカルとして動作確認してみましょう。

まず、任意のフォルダにnupkgファイルを配置します。(例では C:\PackageSource )

続いて動作確認用のプロジェクト(「Blank App(Xamarin.Forms Portable)」)を新規作成します。

ツール > NuGetパッケージ マネージャー > パッケージ マネージャー設定 を開きます。

f:id:ticktack623:20160408152047j:plain

「パッケージ ソース」に新しいパッケージソースを追加修正します。

  • 名前: 任意
  • ソース: nupkgファイルを配置したフォルダ

PCLプロジェクトとAndroidプロジェクトにNuGetパッケージを追加します。この際、パッケージソースを先ほど作ったローカルフォルダに変更してください。

f:id:ticktack623:20160408152759j:plain

PCLプロジェクトのAppクラスを動作確認用に修正します。

public App()
{
    var button = new Button
    {
        Text = "Show dialog"
    };
    var page = new ContentPage
    {
        Content = new StackLayout
        {
            VerticalOptions = LayoutOptions.Center,
            Children = {
                button,
            }
        }
    };

    button.Clicked += (s, e) =>
    {
        page.DisplayAlert("App version" ,
            Plugin.AwesomeFeature.CrossAwesomeFeature.Current.GetAppVersion(),
            "OK");
    };

    MainPage = page;
}

早速Androidを実行してみます。

f:id:ticktack623:20160408161146j:plain:w220 f:id:ticktack623:20160408161204j:plain:w220

ばっちりAndroid固有の実装が動いています!

それでは固有実装を用意していないUWP版を実行するとどうなるでしょう?

f:id:ticktack623:20160408154647j:plain:w400 f:id:ticktack623:20160408154751j:plain

エラー無くビルドできましたが、ボタンをクリックするとNotImplementedExceptionが発生しました。 これはPCLプロジェクトからダミー実装のdllを引き継いだためですね。

Bait and Switch

さて、PCLプロジェクトの出力であるdll(ここではApp3.dll)はAndroid、UWPの両方に全く同じものが同梱されています。 そして、PluginのdllはAndroidとUWPでそれぞれ違うものが同梱されています。(Android:固有実装、UWP:ダミー実装)

これの何が凄いかというと、PCLプロジェクトが「ビルド時に参照していたdll」と「実行時に参照するdll」をすり替えたにもかかわらず、メソッド呼び出しができている点です。 これは「Bait and Switch」と呼ばれるトリックです。(assembly name、version、class構造が一致していればいいらしい?)

「Bait and Switch」実現のテンプレート化こそがPlugins for Xamarinの真骨頂であると言えます。

Androidアプリに固有実装dllが同梱されている点も重要だと思うんですが、これはリンカーの仕様なんでしょうか? (同名dllが自プロジェクトと外部プロジェクト(PCL)で参照されていたら、自プロジェクトの方を優先?)

最後に

NuGetパッケージの仕様など、説明しきれない部分もありましたがPlugins for Xamarinがどんなものか何となく理解していただければ幸いです。

ぜひともクールなプラグインを作ってNuGet公開にチャレンジしてください。

↓の要求事項に応えればPlugins for Xamarinに仲間入りできるみたいですよ?

Requirements of a Plugin

  • Open source on GitHub
  • Documentation on GitHub's README file
  • Name: "FEATURE_NAME Plugin for Xamarin and Windows"
  • Namespace: Plugin.FEATURE_NAME
  • App-store friendly OSS license (we like MIT)
  • No dependency on Xamarin.Forms

リンク

xamarin/plugins: Plugins for Xamarin (GitHub)

The Bait and Switch PCL Trick