はじめに
この記事はXamarin.FormsでAndroid/iOS両対応のアプリを作ってみたい!という気持ちをまとめたものです。
ひと通り触ってみてわかったようなわからないような感じにはなったので、こうして記事としてまとめることでわかったようなほうに寄せていこうというような感じで書かれました。
わたしのように、これから始めてみたいな……とお考えのひとの参考かなにかになれば、と思います。
Xamarinとは?
Xamarinとは
Common Language Infrastructure(CLI)並びにCommon Language Specifications(これらを合わせたものはMicrosoft .NETと同等の環境である)のクロスプラットフォームな実装である。すなわち、それぞれLinuxなどのUnix系OS、iPhoneなどのiOS、Android上で動作する.NET環境である。
(Wikipediaより)
というソフトウェアのことであり、これを開発した企業(現在はMicrosoftの子会社です)の名前でもあります。
また、公式サイトによれば
Deliver native Android, iOS, and Windows apps, using existing skills, teams, and code.
Build native apps for multiple platforms on a shared C# codebase. Use the same IDE, language, and APIs everywhere.
- Native UI, native API access, and native performance
- Anything you can do in Objective-C, Swift, or Java you can do in C# with Xamarin
- Ship cutting-edge apps with same-day support for new OS releases
とのことです。
つまり、ものすごく乱暴にいえば、「C#によってAndroid、iOS、WindowsPhone(とここには書かれていないがWindows、macOS)のアプリをクロスプラットフォーム開発できるよ!Objective-CとかSwiftとかJavaでできることはC#とXamarinでなんでもできるよ!」という感じです。
つよい。
だいじなこと
ところで、Xamarinを使えばC#でAndroid/iOSのクロスプラットフォーム開発ができる!とは言いましたが、もちろんそれだけではどうしようもありません。
どういうことかというと、各プラットフォームで異なる処理に対しては、異なるコードを書くことになるので、それらのプラットフォームに対する知識は必須です。
もし、これまでにAndroidおよびiOSでの開発をまったくやったことがない、という場合にはまずはネイティブ開発をやってみて処理の流れを身につけておく必要があるでしょう(ここでいうネイティブとはAndroidならJava/Kotlin(KotlinはもはやAndroidの公式開発言語になりました。やったね)など、iOSであればObjective-C/Swiftによるアプリの開発のことです)。
また、Visual StudioはWindows版、macOS版がありますが、iOSアプリの開発にはmacOSが、WindowsPhoneアプリの開発にはWindowsがそれぞれ必須です(Androidはもともとどの環境でも開発できます)。Xamarin Live Playerの登場によってWindows環境下でもiOSアプリを(擬似的に)動作させられるようにはなっていますが、これはXLPアプリがインタプリタのような動作をしているもので、じっさいのアプリの動きとは異なる可能性が大いにあるとされます(とはいえ極端に違う、ということもまたないようではありますが)。またアプリをビルドしているわけではないのでこれによって公開することもできません。
以前はiOSアプリの開発に際して、一部(画面の設計等)にXCodeが必要だったようですが現在ではそのようなことはありません。Visual Studio (for Mac)単体で完結できます(iOSのSDKはXCodeに含まれていますのでそのために最新版のXCodeをインストールしておく必要はあります)。
実験環境
こんかい実験に使ったのは
- MacBook Pro (Retina, 13-inch, Early 2015)
- macOS Sierra 10.12.5
- プロセッサ: 3.1 GHz Intel Core i7
- メモリ: 16 GB 1867 MHz DDR3
- Visual Studio for Mac COMMUNITY 7.0 (build 3146)
- XCode 8.3.2 (8E2002)
- Androidエミュレータ: Nexus 6P 7.1.1
- iOSシミュレータ: iPhone7 10.3
という環境です。iOS開発でも遊びたかった(というかそもそもスタートが「Android/iOS両対応のアプリ作ってみたい!」ですが)のでmacOSを選択しています。WindowsPhoneでの開発がしたい!という場合はWindowsを選択しましょう(その場合はiOS開発に際して前述のようなハードルがあります)。
ダウンロードとインストール
さて、前置きはこれくらいにして、さっそく専用IDEであるところのVisual Studio for Macをセットアップしましょう!
※macOSではXamarin Studioを使おう!のような記事が(公式でも)多数見られますが、わたしが調査した範囲ではもう入手できない(?)ようでした。ただし、ブランド以外は変わらないようなので実際上の問題はないでしょう(たぶん。
詳細なセットアップ手順はここでは割愛しますが、インストーラの指示に従えば問題はないでしょう。ここでインストールするコンポーネントのサイズは3GiB超、AndroidSDKも同時にインストールする場合は4GiB超とかなり時間がかかりますので、一狩り行くなどしてヒマを潰しましょう。AndroidSDKがすでにインストールされている場合はそれを使うこともできますのでパスを指定しておきましょう。
ちなみにライセンスは
個人開発者であれば無料のCommunityライセンスで利用することができます。企業の業務としての開発の場合、一定の条件を満たす場合は同じくCommunityライセンスが利用可能なようです。詳しくは公式サイトへ。
……母国語の法解釈でさえ紛糾するのに、英語でライセンスの話はちょっと……という方も多いかと思われます。幸い、日本語でのサポート(別の企業による)がありますのでそちらへ相談するのもいいかもしれません。
わたしも深く理解しているわけではありませんので、もし導入を検討されるのであればいずれにしても問い合わせてみるのがいちばんかと思います。
……そろそろダウンロードも完了したころでしょうか。それではこんにちは世界。こんにちはXamarin。
Hello World!!
やはりまずはこれをやらねば始まりませんね。
というわけでHello World!です。
VS for Macを起動するところからひとつずつ見ていきましょう。以下、画像はクリックで拡大できます。
まずはVS for Macを起動して、New Projectを選択します。
「新しいプロジェクト用のテンプレートを選択します」というダイアログでは
「マルチプラットフォーム > アプリ」、「Xamarin.Forms > Blank Forms App」と選択し、「次へ」。
「Configure your Blank Forms App」というダイアログが表示されるので、アプリの設定を入力していきます。
AppName、Organization Identifierはお好みで設定しましょう。ここではそれぞれ「HelloXamarinApp」、「com.example」としました。
ターゲットプラットフォームはもちろんAndroid、iOSの両方を選択します。
また、Shared Codeに「Use Portable Class Library」を選択し、「Use XAML for user interface files」にもチェックを入れておきます。
設定できたら「次へ」。
お次はプロジェクトの設定です。プロジェクト名やソリューション名、それらの保存場所を設定できます。
ここはデフォルトのままでも問題ないと思います。わたしはこれらはデフォルトのままにしておいて、バージョン管理にgitを使うようにしました。ついでに自動UIテストも有効にしました。
問題なければ「次へ」!
ここまで設定すると、メインの画面が開きますが慌ててはいけません。上の方に「パッケージを追加しています…」と表示されているのがわかると思います。
これが終わるまでは待機です。心を落ち着けて瞑想でもして待ちましょう。わたしのオススメの瞑想法は軟酥(なんそ)の法というものです。そもそも軟酥というのはバター(のようなもの)のことで……おっと完了したようです。
まずはソリューションのビルド
じつはこの状態で、すでにアプリケーションをビルドして実行できるまでの準備は整っています。
やってみましょう。
まずはすべてビルドしてしまいます。メニューから「ビルド(B) > すべてビルド(B) ⌘B」を選択し、ソリューションをビルドします。
これが完了すれば、あとは各プラットフォームのアプリを(ここではエミュレータ/シミュレータに)デプロイ、そして実行するだけです。
Androidの場合
左上のビルドターゲットが「<アプリ名>.Droid」となっていることを確認し、(お好みのターゲットのエミュレータを選択したうえで)実行(►)ボタンをクリックします。しばらくするとエミュレータが起動し、わたしたちの初めてのXamarin.FormsアプリケーションがAndroidで起動するはずです。
もし、「Activity does not exist.」のようなエラーが出るときは、あらためてDroidプロジェクトをビルドしてみてください。また「実行に失敗しました」のようなエラーが出るときは、VS for Macの再起動も試してみてください。困ったら再起動です。
iOSの場合
iOSの場合もほとんど変わりはありません。
左上のビルドターゲットがこんどは「<アプリ名>.iOS」となっていることを確認し、(お好みのターゲットのシミュレータを選択したうえで)実行(►)ボタンをクリックします。しばらくするとシミュレータが起動し、わたしたちの初めての(あるいは2度目の)Xamarin.FormsアプリケーションがiOSで起動するはずです。
Android/iOSのそれぞれのシミュレータの動作イメージがこちらです。
あれ……Hello World!じゃn
Welcome to Xamarin Forms!
コードを1行も書くことなくHello World!できてしまいました。では、少しずつコードを書いてアプリケーションを作っていきましょう。
実機での実行は?
ところで、エミュレータ/シミュレータではなく実機でも動作確認したいというのは当然の要請でしょう。次節で扱うサンプルも実機でしか動作しないAPIを使っていますし……
Androidの場合、とくになにもしなくても、接続してビルドターゲットとして選択するだけで実機にデプロイできます。ただしWindows環境の場合はドライバのインストール等の準備が必要かもしれません。
iOSの場合はデベロッパー登録してあれば同様にしてビルドターゲットとして選択すればよいです。しかしデベロッパー登録がない場合は少々の手間が必要なようです。以下の記事に詳しいです。
Xcode 7 と Xamarin Studio Starter で1円も払わずに自作 iOS アプリを実機確認する (インスパイア記事)
情報はやや古いのですが、(未確認ながら)大筋では問題ないかと思います。ようするにFree Provisioningを利用する、ということのようですね。
わかりやすいサンプルを試す
さて、これからはコードを書いていくことになります。とはいえ、とっかかりがなにもない状態ではどうしようもありませんから、ここではサンプルをみて勉強することにしましょう。
ここで使うのは、Xamarin.Forms公式の Hello, Xamarin.Forms および Hello, Xamarin.Forms Multiscreen です。これらのチュートリアルではPhoneWord(わたしはPhoneNumberAppという名前をつけてしまいました)というアプリの作成を通して
- プラットフォーム固有のコードをどのように扱うか
- ボタンタップ等のUIイベントの処理
- 画面遷移の方法
- ListView の使い方
が学べます。これでひととおり基本のき、くらいまでは押さえられるでしょうか。
PhoneWordというアプリ自体がどういうものなのかはチュートリアルのドキュメントを参照してください。
プラットフォーム固有の処理
このPhoneWordというアプリには変換された番号に電話をかける、という処理があります(Androidのエミュレータ、iOSのシミュレータのどちらも電話をかける処理をサポートしませんが……)。
これはAndroid/iOS間でかなり処理が違うもののひとつです。じっさいにこの部分の(C#の)コードを見てみましょう。
public bool Dial(string phoneNumber)
{
var context = Forms.Context;
if (context == null) {
return false;
}
var intent = new Intent(Intent.ActionCall);
intent.SetData(Uri.Parse("tel:" + phoneNumber));
if (IsIntentAvailable(context, intent)) {
context.StartActivity(intent);
return true;
}
return false;
}
Androidにおいて電話をかけるにはIntent を使います。Intent を生成して、パラメータをセットして、(そのIntent を処理できるService あるいはActivity があることを確認して、)これを投げることで電話をかけることができます。
いっぽうiOSではこうなります。
public bool Dial(string phoneNumber)
{
return UIApplication.SharedApplication.OpenUrl(new NSUrl("tel:" + phoneNumber));
}
あたりまえのようですがぜんぜん違いますね。大事なのはこのような違いをどうやって吸収するか?ということでしょう。
(両メソッドのシグネチャを見て気づいた方もおられるでしょうが)Xamarin.Formsにおいてはこのような場合はinterface を使っていくことになります。つまり、プラットフォーム固有のコードはあるinterfaceを実装し、共有コード部分ではこのinterface を通してのみアクセスすることで共通化しています。
このアプリでは
public interface IDialer
{
bool Dial(string phoneNumber);
}
というinterface を定義しておいて、
public class PhoneDialer: IDialer
{
public bool Dial(string phoneNumber)
{
//...省略
}
}
のように実装しています。これを呼び出す共通部分では
var dialer = DependencyService.Get<IDialer>();
としてIDialer を実装するクラス(のインスタンス)を取得できます。あとは呼び出すだけですね。
dialer.Dial(translatedNumber);
さきほども述べましたとおり、エミュレータ/シミュレータでは電話をかけることはできませんのでこのコードが実行されることはありません(dialer に常にnull が返ります)……
UIイベントの処理
つぎはUIイベントの処理について見ていきましょう。いくつかボタンがありますが、それぞれ押されたときにどのような動作をするか?を定義していきます。
まずは画面のレイアウト定義を見てみます。
「タップされたらテキストボックスに入力された文字列を数字(とハイフン)に変換する」ボタンはこのように定義されています。
<!-- ...省略 -->
<Button x:Name="translateButton" Text="Translate" Clicked="OnTranslateClicked" />
<!-- ...省略 -->
注目するのはClicked 属性です。値としてOnTranslateClicked と指定されています。つまり、このボタンがタップされるとこのメソッドが実行される、というわけです。
そしてそのOnTranslateClicked ですがこういう定義です。
public partial class PhoneNumberAppPage : ContentPage
{
string translatedNumber;
// ...省略
void OnTranslateClicked(object sender, EventArgs ea)
{
translatedNumber = PhonewordTranslator.ToNumber(phoneNumberText.Text);
if(string.IsNullOrWhiteSpace(translatedNumber)) {
callButton.IsEnabled = false;
callButton.Text = "Call";
} else {
callButton.IsEnabled = true;
callButton.Text = "Call " + translatedNumber;
}
}
// ...省略
}
また、UIコンポーネントが持つプロパティはbutton.Property のようにアクセスできる、というのもわかるかと思います。
さらに、呼び出されるメソッドには非同期メソッドも指定可能です。「タップするとダイアログを表示し、そこで”YES”が選ばれたら電話をかける」ボタンは以下のように定義されています。
async void OnCallClicked(object sender, EventArgs ea)
{
if (await this.DisplayAlert("Dial a number",
"Would you like to call " + translatedNumber + "?",
"YES",
"NO")) {
var dialer = DependencyService.Get<IDialer>();
if (dialer != null) {
App.PhoneNumbers.Add(translatedNumber);
callHistoryButton.IsEnabled = true;
dialer.Dial(translatedNumber);
}
}
}
この2つの例ではボタンのタップイベントは静的に定義されていましたが、もちろん動的にイベントハンドラをもたせることもできます。
この場合、コンストラクタでlambda を使って
public PhoneNumberAppPage()
{
InitializeComponent();
callButton.Clicked += async (object sender, EventArgs ea) =>
{
if (await this.DisplayAlert("Dial a number",
"Would you like to call " + translatedNumber + "?",
"YES",
"NO"))
{
var dialer = DependencyService.Get<IDialer>();
if (dialer != null) {
App.PhoneNumbers.Add(translatedNumber);
callHistoryButton.IsEnabled = true;
dialer.Dial(translatedNumber);
}
}
};
}
とやればよいです(型推論によってsender およびea の型は省略可能)。コードからもわかるように、もちろんそれが非同期メソッドであっても問題ありません。
画面の遷移
つづいては画面の遷移ですが、Xamarin.Formsでの画面遷移はかんたんです。遷移先の画面(のクラス(ここではCallHistoryPage ))を用意(xamlおよびxaml.cs)しておいて
await Navigation.PushAsync(new CallHistoryPage());
とするだけです。もちろんこれを呼び出すメソッドはasync である必要があります。
動かしてみるとわかるのですが、どちらの環境でも自動でナビゲーションもいい感じにやってくれます。かんたん!
ListViewの使い方(ちょう基本編)
ListView (UITableView )というとスマホアプリでは頻出のUIで、基本ともいえるかもしれません。
そんなListView ですが、こんかいの例のように「文字列のリストを順に表示させる」だけならば特別なコードを書く必要はまったくありません。
すなわち、
- データソースとなるプロパティを定義する
- XAMLファイルでItemsSource 属性にそのプロパティを指定する
これだけです。見てみましょう。
データソースとなるプロパティを定義し、
public partial class App : Application
{
public static IList<string> PhoneNumbers { get; set; }
// ...省略
}
XAMLファイルでItemsSource 属性にそのプロパティを指定する。
<!-- ...省略 -->
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
Spacing="15">
<!-- ここだけ! -->
<ListView ItemsSource="{x:Static local:App.PhoneNumbers}" />
</StackLayout>
</ContentPage.Content>
<!-- ...省略 -->
これだけで
このようになります。このPhoneNumbers が変更されるにしたがってListView の内容も更新されます。
さらなるステップ
さて、ここまででごくごくかんたんなアプリならばなんとか開発できるようになったような気がしなくもなくもなくなってきたと思います。ですが、もっとちゃんとしたアプリを作りたい!とも思ったのではないでしょうか。
より実践的なサンプルとして Xamarin Dev Days Hands On Lab というものがあります。
さっそく紹介して……といきたいところですが、紙幅の都合上、今回はここまでとしましょう。次回はこのプロジェクトをテーマにしたいと思います。
お楽しみに。では。