April 2018

Volume 33 Number 4

Visual Studio for Mac - Xamarin と Visual Studio for Mac による watchOS 向けのプログラミング

Dawid Borycki

パーソナル アクティビティ トラッカーやスマート ウォッチのような小型のウェアラブル デバイス (Microsoft Band、Android Wear、Apple Watch など) がますます普及しています。こうしたウェアラブルは、装着者の健康状態のパラメーターをリアルタイムに監視するさまざまなセンサーを備えています。通信インターフェイスを装備するウェアラブルもたくさんあります。こうしたデバイスは、カスタム クラウド サービスや専用のクラウド サービス (Microsoft Health など) にセンサー データを簡単に転送して、データの保存や高度な処理を可能にします。そのため、ウェアラブルは、モノのインターネット (IoT) エコシステムの追加エンドポイントとして機能するとも考えられます。また、個人の健康を新たなレベルに引き上げる可能性も秘めています。たとえば、IoT の予測アルゴリズムを使って、今後明らかになりそうな健康上の問題を利用者が事前に把握できる可能性があります。

ウェアラブルではカスタム アプリを実行することもできます。開発者には専用の SDK が提供されます。ただし、多くのモバイル デバイスと同様、プラットフォームごとに固有の API があり、その API にアクセスするにはプラットフォーム固有のプログラミング言語とツールを使用することになります。Xamarin では、ものごとを簡単にするため、Xamarin.Android ライブラリと Xamarin.iOS ライブラリ内で Android Wear と watchOS をそれぞれサポートします。共有 .NET コード ベースを利用すると、モバイル アプリと同様の方法でウェアラブル アプリを開発できるようになります。この共有 .NET コード ベースをプラットフォーム固有のプロジェクトから参照するだけです。

今回は、このアプローチを利用して、図 1 に示す watchOS アプリをビルドする方法を取り上げます。このアプリを実行すると、REST Web サービスからオブジェクトのコレクションの取得を開始します。このコレクションは疑似的な写真で構成され、各写真にはタイトルとビットマップ (単色の画像) が含まれています。開始段階のアプリは、「Get list」というキャプションが付いたボタンを 1 つだけ表示します。このボタンはデータがダウンロードされるまで無効になります (図 1 の 1 行目参照)。

watchOS アプリのプレビュー
図 1 watchOS アプリのプレビュー

このボタンをタップするとアクション シート (図 1 の 2 行目) を表示します。このアクション シートは、アクションまたはアクション ボタンとして定義する複数のボタンで構成するアラートを表示します (bit.ly/2EEUZpL)。この例では、このアクション シートによってアクション ボタンが提示され、そのボタンのキャプション内には表示する写真の範囲が示されています。アクションを 1 つタップすると、選択済みの写真がテーブル コントロール (bit.ly/2Caq0nM) 内の [Get list] ボタンの直下に表示されます (図 1 の最終行を参照)。このテーブルはスクロールできるため、リストを下にスクロールすることでグループ内のすべての写真を表示できます。

今回はここに別の .NET Standard クラス ライブラリ (/ja-jp/dotnet/standard/net-standard) 内の Web サービスとの通信を実装します。参照先のドキュメントによると、.NET Standard は .NET API の正式な仕様で、すべての .NET 実装で利用できるプログラミング インターフェイスへの一貫性のあるアクセスを実現するように設計されています。このアプローチの主なメリットの 1 つは、共有ライブラリを .NET プロジェクト内で簡単に参照できることで、これにより、共有コードの条件付きコンパイルが削減または排除されます。その結果、.NET Standard クラス ライブラリを 1 回実装すると、その後はさまざまな .NET プロジェクトから参照できるようになります。その対象には、ユニバーサル Windows プラットフォーム (UWP)、.NET Core、ASP.NET Core、Xamarin.iOS、Xamarin.Android、Xamarin.Forms などがあり、プラットフォームごとに再コンパイルする必要はありません。

今回は、この共有コードを watchOS アプリで再利用する方法を示します。Web サービスには、疑似的な REST API サーバー JSONPlaceholder (jsonplaceholder.typicode.com、英語) を使用します。このサーバーは、サンプル アプリで使用する写真リソースを含む、複数のリソースを提供します。このリソースは疑似的な写真のコレクションを格納しています。各写真は以下のような JSON オブジェクトとして表されます。

{
  "albumId": 1,
  "id": 1,
  "title": "accusamus beatae ad facilis cum similique qui sunt",
  "url": "http://placehold.it/600/92c952",
  "thumbnailUrl": "http://placehold.it/150/92c952"
}

各写真は、関連付けられた ID、フォト アルバム、タイトル、およびビットマップとそのサムネイルを指す 2 つの URL を含みます。今回の演習では、ビットマップは単なる 1 色の画像で、ビットマップの大きさを示すラベルを付けています。

今回紹介するものはすべて Visual Studio for Mac を使用して作成しています。このプロジェクトの完全なサンプル コードは GitHub (github.com/dawidborycki/Photos、英語) で確認できます。

親アプリと共有コード

一般的な watchOS ソリューションの構造は、3 つのプロジェクトで構成されます (https://developer.apple.com/jp/documentation/General/Conceptual/WatchKitProgrammingGuide/DesigningaWatchKitApp.html#//apple_ref/doc/uid/TP40014969-CH3-SW1 および bit.ly/2EI2dNO を参照)。1 つは、親になる iOS アプリです。他の 2 つのプロジェクトは、Watch アプリ専用の Watch アプリ バンドルと WatchKit Extension バンドルです。親になる iOS アプリは、Watch バンドルをウェアラブルに転送するプロキシに使用します。Watch アプリ バンドルはインターフェイス ストーリーボードを含みます。iOS の場合と同様、開発者はインターフェイス ストーリーボードを使用して、シーンとシーンの間のセグエ (遷移) を定義します。最後に、WatchKit Extension バンドルはリソースとアプリ コードを含みます。

では、新しい単一ビュー iOS プロジェクトを使用して、親 iOS アプリを作成するところから始めます。このプロジェクトは、Visual Studio for Mac の [新しいプロジェクト] クリエーターにあります。プロジェクトとソリューションの名前をそれぞれ Photos.iOS と Photos に設定します。このプロジェクトを作成したら、Photos ソリューションを補完する別のプロジェクト Photos.Common を追加します。このプロジェクトは、.NET Standard ライブラリ プロジェクト テンプレートを使用して作成します。このテンプレートは、[新しいプロジェクト] クリエーターで [マルチプラットフォーム]、[ライブラリ] グループの順に移動するとあります。

.NET Standard ライブラリを作成するときに、.NET Standard のバージョンを選択するオプションが表示されます。このバージョンによって、利用可能な API (バージョンが高いほど、より多くの機能にアクセス可能) とサポート対象のプラットフォーム (バージョンが高いほど、サポート対象のプラットフォームが少ない) が決まります。ここでは、.NET Standard のバージョンを 2.0 に設定します。このバージョンには、HTTP 経由で Web サービスと通信する HttpClient クラスが既に含まれています。/ja-jp/dotnet/api/ にある .NET API ブラウザーを使用すると、.NET Standard バージョンごとの API リストを取得できます。

共有プロジェクトを設定後、1 つの NuGet パッケージ Newtonsoft.JSON をインストールします。これは HTTP 応答の逆シリアル化に使用します。この NuGet パッケージを Visual Studio for Mac にインストールするには、Visual Studio for Windows の場合と同じ手順を実行します。ソリューション エクスプローラーで、[依存関係] を右クリックし、[NuGet] ノードを選択して、コンテキスト メニューから [パッケージを追加] を選択します。Visual Studio に表示されるウィンドウ内で、パッケージを検索します。または、パッケージ コンソールを使用して、コマンド ラインから NuGet パッケージをインストールします。

REST クライアント

これでクライアント クラスを Photos Web サービスに実装する準備が整いました。逆シリアル化を単純にするために、上記の JSON オブジェクトを C# クラスにマップします。この作業は手動で完了するか、専用ツールを使用できます。ここでは、JSONUtils (jsonutils.com、英語) を使用します。この Web サイトには、次の要素で構成された直感的なインターフェイスがあります。

  • クラス名を入力する [Class Name] (クラス名) テキスト ボックス
  • JSON コードまたはその URL を配置する [JSON Text or URL] (JSON テキストまたは URL) テキスト ボックス
  • 言語 (C# や VB.NET など) を選択できる複数のオプション ボタン
  • 2 つのチェック ボックス: [Add Namespace] (名前空間の追加) と [Pascal Case] (パスカル ケース)

C# クラスを生成するために、[Class Name] (クラス名) を「Photo」に設定し、[JSON Text or URL] (JSON テキストまたは URL) テキスト ボックスに jsonplaceholder.typicode.com/photos/1 という URL を貼り付けます。最後に、[Pascal Case] (パスカル ケース) チェック ボックスをオンにし、[Submit] (送信) ボタンをクリックします。生成された Photo クラスがページの下部に表示されるようになります。Photo クラス (ダウンロード コード Photos.Common/Model/Photo.cs を参照) では追加コメントは不要です。このクラスは、上記の JSON オブジェクトを表す自動実装プロパティのみで構成されます。

REST クライアントを実装するために、静的 PhotoClient クラス (Photos.Common プロジェクト) を作成します。このクラスには HttpClient 型のフィールドを 1 つ用意します。このフィールドは、静的コンストラクター内でインスタンスを作成し、JSONPlaceholder URL を指すように BaseAddress プロパティを設定します (次を参照)。

private static HttpClient httpClient;
static PhotosClient()
{
  httpClient = new HttpClient()
  {
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com/")
  };
}

図 2 に示すように、PhotosClient には次の 2 つのパブリック メソッドがあります。

  • GetByAlbumId: HttpClient クラスの GetAsync メソッドを使用して、指定されたアルバムから写真のコレクションを取得します。HTTP 応答の状態コード (CheckStatusCode) を確認した後、ジェネリック ヘルパー メソッド DeserializeResponse を使用して、結果の応答を C# の Photo オブジェクトに逆シリアル化します (ヘルパー メソッドについては後ほど説明します)。
  • GetImageData: 指定した URL の写真を表すバイト配列を取得します。画像データを取得するには、HttpClient クラス インスタンスの GetByteArrayAsync を使用します。

図 2 PhotoClient クラスのパブリック メソッド

public static async Task<IEnumerable<Photo>> GetByAlbumId(int albumId)
{
  var response = await httpClient.GetAsync($"photos?albumId={albumId}");
  var photoCollection = new List<Photo>();
  try
  {
    CheckStatusCode(response);
    photoCollection = await DeserializeResponse<List<Photo>>(response);
  }
  catch(Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
  return photoCollection;
}
public static async Task<byte[]> GetImageData(Photo photo)
{
  var imageData = new byte[0];
  try
  {
    Check.IsNull(photo);
    imageData = await httpClient.GetByteArrayAsync(photo.ThumbnailUrl);
  }
  catch(Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
  return imageData;
}

PhotosClient では、Check­StatusCode と DeserializeResponse という 2 つのプライベート メソッドも実装します。1 つ目のメソッドは、HttpResponseMessage クラスのインスタンスを受け取り、その IsSuccessStatusCode プロパティの値を確認します。状態コードが 200 (成功コード) 以外の場合は例外をスローします。

private static void CheckStatusCode(HttpResponseMessage response)
{
  if (!response.IsSuccessStatusCode)
  {
    throw new Exception($"Unexpected status code: {response.StatusCode}");
  }
}

2 つ目のメソッド DeserializeReponse も HttpResponseMessage クラスのインスタンスを受け取ります。ただし、HttpResponseMessage.Content.ReadAsStringAsync メソッドを使用して、メッセージ本文を文字列として読み取ります。その後、結果の値を Newtonsoft.Json.JsonConvert クラスの静的 DeserializeObject メソッドに渡します。後者は、以下に示すように、指定された型の C# オブジェクトを返します。

private static async Task<T> DeserializeResponse<T>(HttpResponseMessage response)
{
  var jsonString = await response.Content.ReadAsStringAsync();
  return JsonConvert.DeserializeObject<T>(jsonString);
}

図 2 では、カスタム Check クラスの IsNull メソッドも使用しています。IsNull メソッドは簡単な引数の検証を実行し、引数が null かどうかを確認します。null の場合、ArgumentNullException 型の例外をスローします (ダウンロード コードの Photos.Common/Helpers/Check.cs を参照)。

これで共有機能の準備が整います。次は、watchOS アプリの実装に進みます。

watchOS アプリとその構造

実際に watchOS アプリを作成するには、まずソリューション エクスプローラーでソリューション名 (今回の場合は「Photos」) を右クリックし、コンテキストメニューから [追加]、[新しいプロジェクトの追加] の順に選択します。[新しいプロジェクト] ダイアログ ボックスが表示されたら、そこで [watchOS] タブ、[アプリ] タブの順にクリックします ([拡張] タブまたは [ライブラリ] タブは使用しないようにします)。プロジェクト テンプレートのリストが表示され、そこから WatchKit App の C# プロジェクトを選択します。その後、図 3 に示すように、構成可能なオプションのリストが表示されます。

watchOS アプリの構成
図 3 watchOS アプリの構成

前述のように、すべての watchOS アプリには、関連付けられた親 iOS アプリがあります。今回の例では、Photos.iOS がこれに該当します。次に、[アプリ名] テキスト ボックスに「WatchKit」と入力し、[ターゲット] を [watchOS 4.2] に設定して (このリストの個々の項目はインストールしている SDK のバージョンによって異なるため注意してください)、[シーン] グループの下にあるすべてのチェック ボックスをオフにします。

[次へ] ボタンをクリックすると、Visual Studio によって 2 つの追加プロジェクト Photos.iOS.WatchKit および Photos.iOS.WatchKitExtension が作成されます。

1 つ目のプロジェクト (WatchKit) はストーリーボードを含みます。このストーリーボードは、シーンとシーンとの間のセグエ (遷移) の定義に使用します。2 つ目のプロジェクト (WatchKitExtension) には、関連ロジックを含めます。これにはビュー コントローラー (watchOS ではインターフェイス コントローラー) などがあります。そのため、通常はストーリーボード デザイナー (図 4 参照) を使用して Watch アプリの UI を変更します。ストーリーボード デザイナーは、WatchKit アプリの Interface.storyboard ファイルをダブルクリックすることでアクティブになります。

watchOS アプリの UI のデザイン
図 4 watchOS アプリの UI のデザイン

ストーリーボード デザイナーでは、[ツールボックス] からシーンにコントロールをドラッグ アンド ドロップできます。図 4 ではシーンが 1 つだけです。これを、Photos.iOS.WatchKit­Extension プロジェクトの下で定義している InterfaceController クラスに関連付けます。このクラスは iOS アプリの UIViewController のようなものです。つまり、画面上のコンテンツの表示と管理を行い、ユーザー操作を処理するメソッドを実装します。シーンにコントロールを追加したら、[プロパティ] タブを使用してそのプロパティを変更できます。その後、InterfaceController クラスを介してコードからプロパティにアクセスできます。

UI を編集する前に、このクラスの構造を簡単に調査しましょう。このクラスは、すべてのインターフェイス コントローラーの基底クラス WatchKit.WKInterfaceController から派生したものです。InterfaceController の既定の実装では、ビュー ライフサイクルに関連付けられた次の 3 つのメソッドをオーバーライドします (https://developer.apple.com/jp/documentation/General/Conceptual/WatchKitProgrammingGuide/DesigningaWatchKitApp.html#//apple_ref/doc/uid/TP40014969-CH3-SW1 を参照)。

  • Awake: InterfaceController が初期化された直後にシステムから呼び出されます。通常、このメソッドを使用してデータを読み込み、UI を準備します。
  • WillActivate: 関連付けられたビューがアクティブになる直前に呼び出されます。このメソッドを使用して、インターフェイスが表示される直前に最終更新を準備します。
  • DidDeactivate: ビューが非アクティブになるときに呼び出されます。通常、このメソッドを使用して、不要になった動的リソースを解放します。

これらのメソッドを使用して、ビューをその可視性に応じて構成します。簡単な例を示すために、次の Awake メソッドを分析します。

public override void Awake(
  NSObject context)
{
  base.Awake(context);
  SetTitle("Hello, watch!");
}

このコードはまず、基底クラス (WKInterfaceController) の Awake メソッドを呼び出し、次に、ビューの左上隅に表示される文字列を変更する SetTitle メソッドを呼び出します。このタイトルは各インターフェイス コントローラーの既定の要素です。

この変更テストするために、シミュレーターで Photos.iOS.WatchKit アプリを実行できます。まず、Visual Studio for Mac のツール バーにあるドロップダウンを使用して、(Extension バンドルではなく) このアプリをスタートアップ プロジェクトとして設定する必要があります。そのリストの隣には、他に 2 つのドロップダウン リストがあります。1 つは構成 (デバッグまたはリリース) を選択するためのリストで、もう 1 つは選択するシミュレーターのリストです。ここでは、デバッグ構成と、Apple Watch Series 3 (42 ミリ) の watchOS 4.2 のエミュレーターを使用します。このシミュレーターを選択し、[再生] アイコンをクリックすると、アプリがコンパイルされ、配置されます。iOS シミュレーターと、それとペアになる watchOS シミュレーターという 2 つのシミュレーターが起動することに注意してください (上記の図 1 の左側の画面を参照)。

アクション シート

ここからは、Photos.iOS.WatchKit アプリの実際の UI を実装します。図 1 に示したように、このアプリの UI は、ボタン、アクション シート、およびテーブル ビューという 3 つの要素で構成します。ユーザーがボタンをタップすると、アクション シートをアクティブにします。これにより複数のオプションを表示します。これらのオプションを使って、ユーザーはテーブル ビューに表示する写真のグループを選択できます。この写真のグループ化を実装する際に、Apple のガイダンスに従って、アプリ パフォーマンスを改善する 1 つの方法としてテーブル ビューの行数を制限します (https://developer.apple.com/jp/documentation/General/Conceptual/WatchKitProgrammingGuide/Tables.html#//apple_ref/doc/uid/TP40014969-CH14-SW1)。

まず、ボタンのリストを作成します。これはアクション シートに表示します。図 5 に示すように、これらのボタンのリストは、Web サービスから取得した写真のコレクション (photos フィールド) に基づいて生成します。各アクション ボタンは、WatchKit.WKAction クラスのインスタンスとして表現します。このクラスにはパブリック コンストラクターがありませんが、静的 Create メソッドを実装します。このメソッドはアクションの作成に使用します。図 5 に示すように、Create メソッドは次の 3 つの引数を受け取ります。

  • Title: アクション ボタンのキャプションを定義します。
  • Style: アクション ボタンのスタイルを示します。
  • Handler: ユーザーがアクション ボタンをタップしたときに実行するメソッドを指定します。

図 5 写真のタイトルの分割

private const int rowsPerGroup = 10;
private IEnumerable<Photo> photos;
private WKAlertAction[] alertActions;
private void CreateAlertActions()
{
  var actionsCount = photos.Count() / rowsPerGroup;
  alertActions = new WKAlertAction[actionsCount];
  for (var i = 0; i < actionsCount; i++)
  {
    var rowSelection = new RowSelection(
      i, rowsPerGroup, photos.Count());
    var alertAction = WKAlertAction.Create(
      rowSelection.Title,
      WKAlertActionStyle.Default,
      async () => { await DisplaySelectedPhotos(rowSelection); });
    alertActions[i] = alertAction;
  }
}

図 5 では、すべてのアクションに、WatchKit.WKAlertStyle 列挙型の Default 値として表される既定のスタイルがあります。この列挙型では、他に 2 つの値を定義します (apple.co/2EHCAZr、英語)。それは、Cancel と Destructive です。前者は、変更を加えずに操作をキャンセルするアクションの作成に使用します。Destructive スタイルは、元に戻せない変更を行うアクションに適用します。

図 5 の CreateAlertActions メソッドは写真をブロックに分割します。各ブロックは 10 個の要素 (rowsPerGroup 定数) を含みます。選択した写真のグループをコレクションから取得するには、2 つのインデックスが必要です。グループの開始インデックス (beginIndex 変数) と、終了インデックス (endIndex) です。これらのインデックスの計算には、RowSelection クラスを使用します。このクラスは、アクション シートに表示されるアイテムのタイトルを作成する際にも使用します。RowSelection クラスは Photos.iOS.WatchKitExtension プロジェクト (RowSelection.cs) に実装します。その最も重要な部分を図 6 に示します。

図 6 RowSelection クラスを使用したインデックスの計算

public int BeginIndex { get; private set; }
public int EndIndex { get; private set; }
public int RowCount { get; private set; }
public string Title { get; private set; }
private static string titlePrefix = "Elements to show:";
public RowSelection(int groupIndex, int rowsPerGroup, int elementCount)
{
  BeginIndex = groupIndex * rowsPerGroup;
  EndIndex = Math.Min((groupIndex + 1) * rowsPerGroup, elementCount) - 1;
  RowCount = EndIndex - BeginIndex + 1;
  Title = $"{titlePrefix} {BeginIndex}-{EndIndex}";
}

最後に、アクション シートの各ボタンには関連付けられたハンドラーがあり、これが DisplaySelectedPhotos メソッドを呼び出します。このメソッドは、選択された写真のテーブルの表示を担当します。これについては後ほど説明します。

アクション シートをアクティブにするために、まず Photos.Common プロジェクトを参照します。これを行うには、ソリューション エクスプローラーで Photos.iOS.WatchKitExtension の [参照] を右クリックし、[参照の編集]、[プロジェクト] タブの順に選んで、[Photos.Common] を選択します。参照マネージャーに移動したら、Newtonsoft.Json.dll ライブラリを参照して、これが出力ディレクトリにコピーされるようにします。これを行うには、[.NET アセンブリ] タブを使用し、[参照] ボタンをクリックして、フォルダー packages/Newtonsoft.Json/lib/netstandard20 から [Newtonsoft.Json.dll] を選択します。このフォルダーは、Newtonsoft.Json NuGet パッケージのインストール後に作成されます。

これらの手順は、watchOS アプリから共有コード ベース (前に実装した PhotosClient を含む) にアクセスするために必要です。次に、ストーリーボードを使用してこの UI を変更します。watchOS でのレイアウトのしくみに関する詳しい説明は、Apple (https://developer.apple.com/jp/documentation/General/Conceptual/WatchKitProgrammingGuide/CreatingtheUserInterface.html#//apple_ref/doc/uid/TP40014969-CH4-SW1) と Xamarin のドキュメント (bit.ly/2EKjCRM) で確認できます。

ストーリーボード デザイナーを開いたら、[ツールボックス] からボタン コントロールをシーンにドラッグします。[プロパティ] タブを使用して、このボタンの [名前] プロパティと [タイトル] プロパティをそれぞれ「ButtonDisplayPhotoList」と「Get list」に設定します。次に、ユーザーがこのボタンをタップするたびに必ず実行されるイベント ハンドラーを作成します。イベント ハンドラーを作成するには、[プロパティ] タブを使用し、[イベント] タブをクリックして、[アクション] 検索ボックスに「ButtonDisplayPhotoList_Activated」と入力します。Enter キーを押すと、Visual Studio によって InterfaceController クラスの新しいメソッドが宣言されます。最後に、Button­DisplayPhotoList_Activated を次のように定義します。

partial void ButtonDisplayPhotoList_Activated()
{
  PresentAlertController(string.Empty,
    string.Empty,
    WKAlertControllerStyle.ActionSheet,
    alertActions);
}

アクション シートを作成して表示するには、PresentAlertController を使用します。このメソッドは次の 4 つの引数を受け取ります。

  • Title: アラートのタイトルを示します。
  • Message: アラート本文に表示するテキストを指定します。
  • PreferredStyle: アラート コントローラーのスタイルを指定します。スタイルは、Watch­Kit.WKAlertControllerStyle 列挙型で定義された、Alert、SideBy­SideButtonsAlert、または ActionSheet のいずれかの値で表します。これらの違いは、https://developer.apple.com/jp/documentation/General/Conceptual/WatchKitProgrammingGuide/Alerts.html にまとめられています。
  • Actions: アラートに含めるアクション ボタンのコレクションです。アクションの数は、参照先のドキュメントで説明されているとおり、アラート スタイルに応じて変わることに注意してください。

ここでは、タイトルとメッセージを両方とも string.Empty に設定し、アラート スタイルは ActionSheet に設定します。その結果、アクション ボタンのみが表示されます (上記の図 1 を参照)。ユーザーが [Get list] ボタンをタップする前に alertActions の準備が整うように、Awake メソッド内で写真とタイトルを取得します (図 7 参照)。

図 7 写真とタイトルの取得

public async override void Awake(NSObject context)
{
  base.Awake(context);
  SetTitle("Hello, watch!");
  // Disable button until the photos are downloaded
  ButtonDisplayPhotoList.SetEnabled(false);
  // Get photos from the web service (first album only)
  photos = await PhotosClient.GetByAlbumId(1);
  // Create actions for the alert
  CreateAlertActions();
  ButtonDisplayPhotoList.SetEnabled(true);
}

これでアプリを実行できます。リモート サーバーからフォト アルバムが取得されるとボタンが有効になります。これをクリックすることで、アクション シートがアクティブになります (図 1 の真ん中の部分を参照)。

テーブル ビュー

この実装を完成するには、選択された写真のグループを表示するテーブル ビューを作成する必要があります。そのために、ストーリーボード デザイナーを開き、[ツールボックス] からテーブル コントロールをシーンにドラッグします (ここでは [Get list] ボタンのすぐ下に配置します)。

次に、セル レイアウトを定義します。既定では、このレイアウトにはコントロールが 1 つあります。それはグループです。これを他のコントロールの親として使用できます。ただし、まず、このグループ コントロールがアクティブになっていることを確認する必要があります。そのため、[ドキュメント アウトライン] タブ (図 4 の下部に表示) をクリックしてから、[Table/Table Row] (テーブル/テーブル行) の下にある [Group Control] (グループ コントロール) をクリックします。次に、画像コントロールとラベル コントロールをテーブルにドラッグします。画像とラベルがテーブル ビューに表示され、[ドキュメント アウトライン] にも表示されます。すべてのコントロール プロパティを次のように構成します。画像 (名前: ImagePhotoPreview、サイズ: 幅と高さを 50 px に固定)、ラベル (名前: LabelPhotoTitle)、テーブル (名前: TablePhotos)、グループ (サイズ: 高さを 50 px に固定)。

コードからコントロール名を簡単に参照できるように、コントロール名を明示的に設定することが重要です。コントロール名を指定すると、Visual Studio によって InterfaceController.designer.cs ファイル内で適切に宣言されます。

watchOS では、すべての行に専用の行コントローラーがあり、これを使用して行の表示を制御できます。ここでは、このコントローラーを使用して、各行の画像とラベルのコンテンツを指定します。行コントローラーを作成するには、[ドキュメント アウトライン] タブで [テーブル行] を選択し、次に [プロパティ] タブを開いて、[クラス] テキスト ボックスに「PhotoRowController」と入力します。新しいファイル PhotoRowController.cs が追加されます。これには同じ名前のクラスが含まれています。その後、次のように、別のメソッドでこのクラスの定義を補います。

public async Task SetElement(Photo photo)
{
  Check.IsNull(photo);
  // Retrieve image data and use it to create UIImage
  var imageData = await PhotosClient.GetImageData(photo);
  var image = UIImage.LoadFromData(NSData.FromArray(imageData));
  // Set image and title
  ImagePhotoPreview.SetImage(image);
  LabelPhotoTitle.SetText(photo.Title);
}

SetElement 関数は Photo 型の引数を 1 つ受け取り、テーブル ビューの適切な行に写真のタイトルとサムネイルを並べて表示します。次に、テーブル行を実際に読み込んで構成するために、次のメソッドを使用して InterfaceController の定義を拡張します。

private async Task DisplaySelectedPhotos(RowSelection rowSelection)
{
  TablePhotos.SetNumberOfRows(rowSelection.RowCount, "default");
  for (int i = rowSelection.BeginIndex, j = 0;
    i <= rowSelection.EndIndex; i++, j++)
  {
    var elementRow = (PhotoRowController)TablePhotos.GetRowController(j);
    await elementRow.SetElement(photos.ElementAt(i));
  }
}

表示する行について必要な情報を提供するために、RowSelection を DisplaySelectedPhotos メソッドに渡します。さらに具体的には、RowCount プロパティを使用して、テーブルに追加する行数を設定します (TablePhotos.SetNumberOfRows)。その後、DisplaySelectedPhotos でテーブル行を反復処理し、各行のコンテンツを設定します。反復ごとに、まず現在の行に関連付けられた PhotoRowController への参照を取得します。その参照が渡されたら、画像のデータとタイトルを取得するために PhotoRowController.SetElement メソッドを呼び出します。このメソッドが、画像のデータとタイトルをテーブルのセルに表示します。

最後に、アプリを実行すると、上記の図 1 に示した結果が表示されます。

まとめ

今回は、Xamarin、Visual Studio for Mac、および .NET Standard クラス ライブラリ内に実装された共有 C# .NET コード ベースを使用して、watchOS アプリを開発する方法を説明しました。その過程で、アプリの構造、インターフェイス コントローラー、選択された UI コントロール (ボタン、アクション シート、テーブル ビュー) など、watchOS アプリの最も重要な要素をいくつか紹介しました。共有コード ベースはモバイル アプリと同じ方法を使用して実装されるため、モバイル ソリューションを目標のスマート ウェアラブルにまで簡単に拡張できます。Apple が提供する watchOS の詳細については、https://developer.apple.com/jp/documentation/General/Conceptual/WatchKitProgrammingGuide/index.html を参照してください。また、Xamarin のドキュメントについては、bit.ly/2ohSwLU を参照してください。


Dawid Borycki はソフトウェア エンジニアであり、生物医学研究者、作家、および壇上演説家でもあります。彼はソフトウェアの実験とプロトタイプ開発のための新しいテクノロジの学習を楽しんでいます。

この記事のレビューに協力してくれた技術スタッフの Brad Umbaugh に心より感謝いたします。
Brad Umbaugh は、マイクロソフトで Xamarin チーム向けに iOS と関連ドキュメントを執筆しています。彼には Twitter (@bradumbaugh、英語のみ) から連絡できます。Twitter では主に下手なだじゃれをリツイートしています。


この記事について MSDN マガジン フォーラムで議論する