第3回:Xamarinにおけるコードの共通化とプラットフォーム固有のコードの記述特集:Xamarin+Visual Studioで始めるiOS/Android/UWPアプリ開発(2/3 ページ)

» 2016年12月02日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]

2. 共通コード内でプラットフォーム個別コードを使う

 これにはさまざまな手段が用意されている。

 共有プロジェクトでは、#ifディレクティブが使える。また、以下に示すOnPlatformやCustom Rendererなども利用できる。

 PCLプロジェクトでは次のような手段が利用できる。本稿では、先頭から3つを取り上げて解説していく。

  • OnPlatform: プラットフォームに応じて処理を分岐させる。PCLプロジェクトでは、頻繁に利用する
  • DependencyService: DIコンテナのような方法で、プラットフォームごとのロジックをPCLに差し込む
  • Custom Renderer: Xamarin.Forms用に、独自のUIコントロールを作成する
  • Plugins for Xamarin: PCLにもプラットフォームごとのプロジェクトにも同名のDLLを作成し、パッケージにはプラットフォームごとのDLLを含めることで、PCLの仕組みを「だます」方法。Plugin For Xamarin Templatesを使って作成する
  • Effects: Xamarin.Forms用に、既存のUIコントロールに後付けする形でカスタマイズする。Custom Rendererより手軽(Xamarin.Forms 2.1以降)

#ifディレクティブを使って処理を分岐させる

 共有プロジェクト(Shared Project)の中では、#ifディレクティブを使ってプラットフォームごとの処理を切り替えられる。

 例として、プラットフォームごとに異なる文字列を表示してみよう。先ほど作った共有プロジェクトによるXamarin.Formsのプロジェクトを、以下のように変更する。

 まず、共有プロジェクトの「MainPage.xaml」を開き、Labelコントロールに「Label1」と名前を付ける(次のコード)。

<?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:SharedPrj"
             x:Class="SharedPrj.MainPage">
  <!--<Label Text="Welcome to Xamarin Forms!"
           VerticalOptions="Center"
           HorizontalOptions="Center" />-->
  <Label x:Name="Label1"
         FontSize="Large" TextColor="Green"
         VerticalOptions="Center"
         HorizontalOptions="Center" />
</ContentPage>

Labelコントロールを修正する(XAML)
太字の部分を修正した。Labelコントロールに名前を付け、Text属性は削除し、その他にFontSize属性なども追加した(#ifディレクティブの効果を確認するだけなら、名前を付けるだけでよい)。

 続いて、コードビハインド「MainPage.xaml.cs」を開き、次のコードのように編集する。

……省略……
public MainPage()
{
  InitializeComponent();

#if __IOS__
  // iOS用のコード
  Label1.Text = "Hello, iOS!";
#elif __ANDROID__
  // Android用のコード
  Label1.Text = "Hello, Android!";
#elif WINDOWS_UWP
  // UWP用のコード
  Label1.Text = "Hello, UWP!";
#elif WINDOWS_APP
  // Windows 8.1用のコード
  Label1.Text = "Hello, Windows!";
#elif WINDOWS__PHONE_APP
  // Windows Phone 8.1用のコード
  Label1.Text = "Hello, Windows Phone!";
#endif
}
……省略……

#ifディレクティブを使ってプラットフォームごとの処理を記述する(C#)
太字の部分を追加した。
共有プロジェクト中のコードでは、このようにしてプラットフォームごとのコードを切り分けられる。

 上のコードに登場する「__IOS__」などの条件付きコンパイルシンボルは、プロジェクトの作成時に自動生成されている。そのため、このように#ifディレクティブを使ってプラットフォームごとのコードを記述できるのだ。

 なお、#ifディレクティブの中では、プラットフォーム固有のAPIも呼び出せる。プラットフォームごとにロジックを切り替えるには最強の方法といえるが、乱用するとコードが読みにくいものになりかねない。プラットフォームごとの処理はできるだけプラットフォームごとのプロジェクトにメソッドやクラスとしてまとめておき、共有プロジェクトの#ifディレクティブではそのメソッドを呼び出すだけにするといった工夫が必要だ。

 上のコードを実行してみると次の画像のようになる。

#ifディレクティブを使って処理を分岐する実行例(Visual Studio Emulator for Android)
#ifディレクティブを使って処理を分岐する実行例(Mac上のSimulator)
#ifディレクティブを使って処理を分岐する実行例(Mobile Emulator) #ifディレクティブを使って処理を分岐する実行例
上はVisual Studio Emulator for Androidでの実行結果。中はMac上のSimulatorでの実行結果。下はMobile Emulator(Windows 10)での実行結果。なお、Remoted iOS Simulator for Windowsを使用するにはVisual Studio Enterprise Editionのライセンスが必要となった(編集者がCommunity Editionを使っているため、本稿ではMac上のSimulatorでの画面キャプチャーとなっている)。使用方法についてはプレビュー段階の記事だが「XamarinアプリのMacでのビルドとiOS Simulator for Windows」を参照されたい。

OnPlatformで処理を分岐させる

 この方法は、Xamarin.FormsのXAMLコードで頻繁に使われる。

 OnPlatform<T>クラス(Xamarin.Forms名前空間)は、コードビハインドでもXAMLコードでも利用できる。PCLプロジェクトの場合には、OnPlatformでプラットフォーム依存のAPIは利用できない(共有プロジェクトでは利用できるが、コードビハインドでは#ifディレクティブを使った方が簡単だ)。

 例えば、XAMLコードでLabelコントロールの色をプラットフォームごとに変えるには、次のコードのようにする。

<Label Text="Welcome to Xamarin Forms!" 
        x:Name="Label1" FontSize="Large">
  <Label.TextColor>
    <OnPlatform x:TypeArguments="Color"
                iOS="Red" Android="Blue" WinPhone="Green"
                />
  </Label.TextColor>
</Label>

OnPlatformでLabelの色をプラットフォームごとに変える(XAML)

 また、コードビハインドで、プラットフォームごとに異なる文字列を設定するには、次のコードのようにする。

// 【1】一般的な書き方
string s  = Device.OnPlatform("iOS", "Android", "Windows");

// 【2】名前付き引数で書くと、分かりやすい
string s = Device.OnPlatform(
             iOS: "iOS",
             Android: "Android",
             WinPhone: "Windows"
           );

// 【3】OnPlatform<T>クラスを直接インスタンス化してもよい
var s = new OnPlatform<string>()
            {
              iOS = "iOS",
              Android = "Android",
              WinPhone = "Windows",
            };

OnPlatformで文字列をプラットフォームごとに変える(C#)
コードビハインドでは、OnPlatform<T>クラスを直接インスタンス化してもよいが、DeviceクラスのOnPlatformメソッドを利用するのが一般的だ。

 OnPlatformの使い方については、「.NET TIPS:Xamarin.Forms:プラットフォームに応じて画面の一部を変えるには?」で詳しく解説している。

DependencyServiceでプラットフォームごとにロジックを提供する

 これはちょっと長い説明になるが、PCLプロジェクトでプラットフォームに依存するAPIを利用するロジックを書くには必須のテクニックである。

 DependencyServiceクラス(Xamarin.Forms名前空間)は一種のDIコンテナであり、プラットフォームごとに実装したオブジェクトをPCL内から利用できる。DependencyServiceクラスを使うコーディングの手順は、次のようになる。

  1. インタフェースをPCLプロジェクトに定義する
  2. プラットフォームごとに、インタフェースを実装するクラスを記述する。そのクラスには、Dependency属性(Xamarin.Forms名前空間)を付ける
  3. PCLプロジェクト内で、DependencyServiceクラスのGetメソッドを使い、プラットフォームに依存するオブジェクトを得る

 例として、OSのバージョンに関する文字列を取得してみよう。

1. インタフェースを定義する

 まず、PCLプロジェクトにインタフェースを定義する。インタフェース名は自由に決めてよいが、ここでは「IPlatformInfo」インタフェースとし、次のコードのように記述する。

namespace Xamarin101
{
  // DependencyServiceで使うインタフェース
  public interface IPlatformInfo
  {
    // OSのバージョン文字列を返すプロパティ(読み取り専用)
    string OsVersion { get; }
  }
}

PCLにインタフェースを定義する(C#)
DependencyServiceでは、メソッドだけでなく、このようにプロパティも利用できる。

2. プラットフォームごとにインタフェースを実装する

 上で定義したインタフェースの実装として、プラットフォームごとに「PlatformInfo」クラスを記述していく。

 そのとき、ソースコードの冒頭にDependency属性の指定が必要だ。それは、「このアセンブリには、DependencyServiceで使うPlatformInfoクラスがありますよ」といった意味である。

 まず、Androidの実装(次のコード)。

[assembly: Xamarin.Forms.Dependency(typeof(Xamarin101.Droid.PlatformInfo))]
namespace Xamarin101.Droid
{
  // DependencyServiceで使う実装(Android)
  public class PlatformInfo : IPlatformInfo
  {
    public string OsVersion
    {
      get { return Android.OS.Build.VERSION.Release; }
    }
  }
}

Androidの実装クラス(C#)
Androidのプロジェクトに記述する。
Android.OS名前空間のBuildクラスを使って情報を取得している。このクラスは、Androidに固有のもので、PCL内に記述できない。

 次のコードがiOSのものだ。

[assembly: Xamarin.Forms.Dependency(typeof(Xamarin101.iOS.PlatformInfo))]
namespace Xamarin101.iOS
{
  // DependencyServiceで使う実装(iOS)
  public class PlatformInfo : IPlatformInfo
  {
    public string OsVersion
    {
      get
      {
        var device = UIKit.UIDevice.CurrentDevice;
        return $"{device.SystemName} {device.SystemVersion}";
      }
    }
  }
}

iOSの実装クラス(C#)
iOSのプロジェクトに記述する。
UIKit名前空間のUIDeviceクラスを使って情報を取得している。このクラスは、iOSに固有のもので、PCL内に記述できない。

 そして、UWP用のコードは次のようになる。

using Windows.Security.ExchangeActiveSyncProvisioning;
[assembly: Xamarin.Forms.Dependency(typeof(Xamarin101.UWP.PlatformInfo))]
namespace Xamarin101.UWP
{
  // DependencyServiceで使う実装(UWP)
  public class PlatformInfo : IPlatformInfo
  {
    var devInfo = new EasClientDeviceInformation();
    public string OsVersion
    {
      get { return devInfo.OperatingSystem; }
    }
  }
}

UWPの実装クラス(C#)
UWPのプロジェクトに記述する。
Windows.Security.ExchangeActiveSyncProvisioning名前空間のEasClientDeviceInformationクラスを使って情報を取得している。このクラスは、UWPに固有のもので、PCL内に記述できない。

3. DependencyServiceを使う

 最後に、PCLで次のコードのようにしてプラットフォームに依存するコードを使う。

// DependencyServiceから、IPlatformInfoオブジェクトを取得する
IPlatformInfo platformInfo = DependencyService.Get<IPlatformInfo>();

// 取得したオブジェクトを使う
string version = platformInfo.OsVersion;

PCLでDependencyServiceを使う例(C#)
プラットフォームに依存する実装を取得するには、DependencyServiceクラスのGetメソッドに、必要とするオブジェクトのインタフェースを型引数として与える。

 なお、以上のDependencyServiceの使い方については、「.NET TIPS:Xamarin.Forms:プラットフォームに依存する処理を書くには?」で詳しく解説している。また、ユーザーデータの読み書きを、DependencyServiceではなくPCL Storageを使うことで簡単に実装する方法を「.NET TIPS:Xamarin.Forms:ユーザーデータを保存するには?」で解説している。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。