Xamarin.iOS の MonoTouch.Dialog の概要
MonoTouch.Dialog (略して MT.D) は、ビュー コントローラーやテーブルなどの面倒な作成というよりは、開発者が情報を使用してアプリケーション画面とナビゲーションの構築ができるようにする迅速な UI 開発ツールキットです。そのため、大幅な UI 開発の簡略化とコードの削減が行われます。 たとえば、次のスクリーンショットを考えてみましょう。
この画面全体を定義するために、次のコードが使用されました。
public enum Category
{
Travel,
Lodging,
Books
}
public class Expense
{
[Section("Expense Entry")]
[Entry("Enter expense name")]
public string Name;
[Section("Expense Details")]
[Caption("Description")]
[Entry]
public string Details;
[Checkbox]
public bool IsApproved = true;
[Caption("Category")]
public Category ExpenseCategory;
}
iOS でテーブルを操作する場合、多くの場合、繰り返し出現する多数のコードが存在します。 たとえば、テーブルが必要になるたびに、そのテーブルに設定するためのデータ ソースが必要になります。 ナビゲーション コントローラーを介して接続されている 2 つのテーブルベースの画面があるアプリケーションでは、各画面で同じコードが多数共有されます。
MT.D は、そのコードをすべてテーブル作成用の汎用 API にカプセル化することで、簡略化を行います。 次に、オブジェクト バインディングの宣言構文が考慮に入れられた抽象化がその API に対して提供され、さらにこれが簡単になります。 このようにして、MT.D で 2 つの API が使用できるようになります。
- 低レベルの Elements API – "Elements API" は、画面とそのコンポーネントを表す要素の階層ツリーの作成に基づいています。 Elements API を使用すると、開発者は UI を作成する際の柔軟性と制御を最大限に高めることができます。 さらに、Elements API には JSON による宣言定義の高度なサポートがあります。これにより、迅速な宣言と、サーバーからの動的 UI 生成の両方が可能になります。
- 高レベルリフレクション API – クラスに UI ヒントと MT で注釈が付けられた Binding API とも呼ばれます。D は、オブジェクトに基づいて画面を自動的に作成し、画面上に表示される内容 (および必要に応じて編集) と、基になるオブジェクトバッキングとの間のバインディングを提供します。 上の例は、Reflection API の使用を示しています。 この API では、Elements API が行うきめ細かな制御は行われませんが、クラス属性に基づいて要素の階層を自動的に構築することで、さらに複雑さが軽減されます。
MT.D には、画面作成用の組み込み UI 要素の大規模なセットが含まれていますが、カスタマイズされた要素と高度な画面レイアウトの必要性も認識しています。 そのため、拡張性は API に組み込まれているファーストクラスの機能です。 開発者は、既存の要素を拡張したり、新しい要素を作成したりして、シームレスに統合することができます。
さらに、MT.D には、"プルして更新" のサポート、非同期の画像読み込み、検索のサポートなど、多数の一般的な iOS UX 機能が組み込まれています。
この記事では、次のような MT.D の操作について包括的に説明します。
- MT.D コンポーネント – MT.D を構成するクラスの理解に焦点を当て、迅速に把握できるようにします。
- 要素リファレンス – MT.D の組み込み要素の包括的な一覧。
- 高度な使用法 – プルして更新、検索、バックグラウンドでの画像の読み込み、LINQ を使用した要素の階層の構築、MT.D で使用するカスタム要素、セル、コントローラーの作成などの高度な機能について説明します。
MT.D の設定
MT.D は Xamarin.iOS と共に配布されます。 これを使用するには、Visual Studio 2017 または Visual Studio for Mac で Xamarin.iOS プロジェクトの [参照] ノードを右クリックし、MonoTouch.Dialog-1 アセンブリへの参照を追加します。 次に、必要に応じてソース コードに using MonoTouch.Dialog
ステートメントを追加します。
MT.D について
Reflection API を使用している場合でも、Elements API を介して直接作成された場合と同様に、MT.D によって内部に要素の階層が作成されます。 また、前のセクションで説明した JSON のサポートにより、Element も作成されます。 このため、MT.D の構成要素について基本的に理解しておくことが重要です。
MT.D は、次の 4 つの部分を使用して画面を構築します。
- DialogViewController
- RootElement
- セクション
- 要素
DialogViewController
DialogViewController (略して DVC) は、UITableViewController
から継承されるため、テーブルを使用して画面を表します。 DVC は、通常の UITableViewController と同様にナビゲーション コントローラーにプッシュできます。
RootElement
RootElement は、DVC に入る項目の最上位レベルのコンテナーです。 これには Section が含まれ、これには Element を含めることができます。 RootElement はレンダリングされません。これらは実際にレンダリングされるものの単なるコンテナーです。 RootElement が DVC に割り当てられ、DVC によってその子がレンダリングされます。
セクション
セクションは、テーブル内のセルのグループです。 通常のテーブル セクションと同様、次のスクリーンショットのように必要に応じてヘッダーとフッターを指定して、テキストまたはカスタム ビューにすることができます。
要素
Element は、テーブル内の実際のセルを表します。 MT.D には、さまざまなデータ型またはさまざまな入力を表す多様な Element が用意されています。 たとえば、次のスクリーンショットは、使用可能ないくつかの要素を示しています。
Section と RootElement の詳細
次に、RootElement と Section について詳しく説明します。
RootElement
MonoTouch.Dialog プロセスを開始するには、少なくとも 1 つの RootElement が必要です。
RootElement がセクションまたは要素値で初期化されている場合、この値は、構成の概要を示す子 Element を特定するために使用されます。これは、ディスプレイの右側にレンダリングされます。 たとえば、次のスクリーンショットには、左側にテーブルと、右側に詳細画面のタイトルが含まれるセル、つまり "デザート" と選択したデザートの値が示されています。
上記のように、ルート要素を Section 内で使用して、新しい入れ子になった構成ページの読み込みをトリガーすることもできます。 このモードで使用すると、指定されたキャプションは、セクション内でレンダリングされている間に使用され、サブページのタイトルとしても使用されます。 次に例を示します。
var root = new RootElement ("Meals") {
new Section ("Dinner") {
new RootElement ("Dessert", new RadioGroup ("dessert", 2)) {
new Section () {
new RadioElement ("Ice Cream", "dessert"),
new RadioElement ("Milkshake", "dessert"),
new RadioElement ("Chocolate Cake", "dessert")
}
}
}
};
上の例では、ユーザーが "デザート" をタップすると、MonoTouch.Dialog によって新しいページが作成され、ルートが "デザート" になり、3 つの値を持つラジオ グループを持つページに移動します。
この特定のサンプルでは、RadioGroup に値 "2" を渡したため、ラジオ グループでは "デザート" セクションで "チョコレート ケーキ" が選択されます。 これは、リストの 3 番目の項目 (ゼロインデックス) を選択することを意味します。
Add メソッドを呼び出すか、C# 4 の初期化子構文を使用するとセクションが追加されます。 Insert メソッドは、アニメーションを含むセクションを挿入するために用意されています。
Group インスタンス (RadioGroup ではなく) を使用して RootElement を作成した場合、Section に表示される場合の RootElement の集計値は、Group.Key 値と同じキーを持つすべての BooleanElement および CheckboxElement の累積数になります。
セクション
Section は、画面の要素をグループ化するために使用され、RootElement の唯一の有効な直接の子です。 Section には、新しい RootElement を含む任意の標準要素を含めることができます。
セクションに埋め込まれた RootElement は、新しい深いレベルに移動するために使用されます。
Section には、文字列として、または UIView としてヘッダーとフッターを含めることができます。 通常は文字列のみを使用しますが、カスタム UI を作成するには、ヘッダーまたはフッターとして任意の UIView を使用できます。 文字列を使用して、次のように作成できます。
var section = new Section ("Header", "Footer");
ビューを使用するには、次のようにビューをコンストラクターに渡します。
var header = new UIImageView (Image.FromFile ("sample.png"));
var section = new Section (header);
通知を受け取る
NSAction の処理
MT.D には、コールバックを処理するためのデリゲートとして NSAction
が用意されています。
たとえば、MT.D によって作成されたテーブル セルのタッチ イベントを処理するとします。 MT.D を使用して要素を作成する場合、次に示すように、単純にコールバック関数を指定します。
new Section () {
new StringElement ("Demo Callback", delegate { Console.WriteLine ("Handled"); })
}
要素値の取得
Element.Value
プロパティと組み合わせることで、コールバックは他の要素で設定された値を取得できます。 表すクレソン、ダン橄欖岩製品構文解析木作法:
var element = new EntryElement (task.Name, "Enter task description", task.Description);
var taskElement = new RootElement (task.Name) {
new Section () { element },
new Section () { new DateElement ("Due Date", task.DueDate) },
new Section ("Demo Retrieving Element Value") {
new StringElement ("Output Task Description", delegate { Console.WriteLine (element.Value); })
}
};
このコードにより、次に示すような UI が作成されます。 この例の完全なチュートリアルについては、Elements API チュートリアルを参照してください。
ユーザーが一番下のテーブル セルを押すと、匿名関数のコードが実行され、element
インスタンスの値が Visual Studio for Mac の [アプリケーション出力] パッドに書き込まれます。
組み込み要素
MT.D には、Element と呼ばれる多数の組み込みのテーブル セル項目が付属しています。 これらの要素を使用して、テーブルのセルにさまざまな型 (文字列、浮動小数点数、日付、画像など) を表示することができます。 各要素によって、データ型が適切に表示されます。 たとえば、ブール値要素によって、その値を切り替えるスイッチが表示されます。 同様に、float 要素によって、浮動小数点値を変更するスライダーが表示されます。
画像や html などのより豊富なデータ型をサポートするための、さらに複雑な要素があります。 たとえば、選択すると WEB ページを読み込む UIWebView が開かれる html 要素によって、テーブル セルにキャプションが表示されます。
要素値の操作
ユーザー入力をキャプチャするために使用される要素では、任意の時点での要素の現在の値を保持するパブリック Value
プロパティを公開しています。 ユーザーがアプリケーションを使用すると、自動的に更新されます。
これは MonoTouch.Dialog の一部であるすべての Element の動作ですが、ユーザーが作成した要素では必要ありません。
String 要素
StringElement
には、テーブル セルの左側にキャプションが表示され、セルの右側に文字列値が表示されます。
StringElement
をボタンとして使用するには、デリゲートを指定します。
new StringElement ("Click me", () => {
new UIAlertView("Tapped", "String Element Tapped", null, "ok", null).Show();
});
Styled String 要素
StyledStringElement
を使用すると、組み込みのテーブル セル スタイルまたはカスタム書式を使用して文字列を表示できます。
StyledStringElement
クラスは StringElement
から派生しますが、フォント、テキストの色、セルの背景色、改行モード、表示する行数、アクセサリを表示するかどうかなど、いくつかのプロパティのカスタマイズを開発者は行うことができます。
Multiline 要素
Entry 要素
名前が示すように、EntryElement
はユーザー入力を取得するために使用されます。 通常の文字列または文字が非表示になっているパスワードがサポートされます。
これは、次の 3 つの値で初期化されます。
- ユーザーに表示される入力のキャプション。
- プレースホルダー テキスト (これは灰色で表示されたテキストで、ユーザーにヒントを提供します)。
- テキストの値です。
プレースホルダーと値には null を指定できます。 ただし、キャプションは必須です。
任意の時点で、その Value プロパティにアクセスすると、EntryElement
の値を取得できます。
さらに作成時に、KeyboardType
プロパティをデータ入力に必要なキーボードの種類のスタイルに設定できます。 これは、次に示すように、UIKeyboardType
の値を使用してキーボードを構成するために使用できます。
- 数値
- 電話番号
- URL
- メール
Boolean 要素
Checkbox 要素
Radio 要素
RadioElement
では、RadioGroup
を RootElement
で指定する必要があります。
mtRoot = new RootElement ("Demos", new RadioGroup("MyGroup", 0));
RootElements
は、ラジオ要素を調整するためにも使用されます。 RadioElement
メンバーは複数の Section にわたって存在することができます (たとえば、着信音セレクターのようなものを実装し、カスタム着信音をシステム着信音から分離する)。 概要ビューには、現在選択されているラジオ要素が表示されます。 これを使用するには、次のようにグループ コンストラクターを使用して RootElement
を作成します。
var root = new RootElement ("Meals", new RadioGroup ("myGroup", 0));
RadioGroup
のグループの名前は、含まれているページで選択される値 (存在する場合) を表示するために使用され、値 (この場合は 0) は、最初の選択項目のインデックスです。
Badge 要素
Float 要素
Activity 要素
Date 要素
DateElement に対応するセルを選択すると、次に示すように日付の選択が表示されます。
Time 要素
TimeElement に対応するセルを選択すると、次に示すように時刻の選択が表示されます。
DateTime 要素
DateTimeElement に対応するセルを選択すると、次に示すように日付/時刻の選択が表示されます。
HTML 要素
HTMLElement
によってその Caption
プロパティの値がテーブル セル内に表示されます。 選択されている場合、要素に割り当てられている Url
が、次に示すように UIWebView
コントロールに読み込まれます。
Message 要素
Load More 要素
この要素を使用すると、ユーザーがリスト内の項目をさらに読み込むことができます。 通常と読み込みのキャプション、およびフォントとテキストの色をカスタマイズできます。
ユーザーがセルをタップすると、UIActivity
インジケーターのアニメーション化が開始され、読み込み中のキャプションが表示され、コンストラクターに渡された NSAction
が実行されます。 NSAction
のコードが完了すると、UIActivity
インジケーターのアニメーション化が停止し、通常のキャプションが再び表示されます。
UIView 要素
さらに、UIViewElement
を使用して任意のカスタム UIView
を表示できます。
Owner-Drawn 要素
この要素は抽象クラスであるため、サブクラス化する必要があります。 要素の高さを返す Height(RectangleF bounds)
メソッドと、特定の境界内でカスタマイズされたすべての描画を行う Draw(RectangleF bounds, CGContext context, UIView view)
を、コンテキストおよびビュー パラメーターを使用してオーバーライドする必要があります。 この要素によって UIView
がサブクラス化され、返されるセルにそれが配置されるという面倒な作業が行われるため、ユーザーが行うのは 2 つの単純なオーバーライドの実装だけです。 DemoOwnerDrawnElement.cs
ファイルのサンプル アプリでは、より優れたサンプル実装を確認できます。
クラスを実装する非常に簡単な例を次に示します。
public class SampleOwnerDrawnElement : OwnerDrawnElement
{
public SampleOwnerDrawnElement (string text) : base(UITableViewCellStyle.Default, "sampleOwnerDrawnElement")
{
this.Text = text;
}
public string Text { get; set; }
public override void Draw (RectangleF bounds, CGContext context, UIView view)
{
UIColor.White.SetFill();
context.FillRect(bounds);
UIColor.Black.SetColor();
view.DrawString(this.Text, new RectangleF(10, 15, bounds.Width - 20, bounds.Height - 30), UIFont.BoldSystemFontOfSize(14.0f), UILineBreakMode.TailTruncation);
}
public override float Height (RectangleF bounds)
{
return 44.0f;
}
}
JSON 要素
JsonElement
は RootElement
のサブクラスで、ローカルまたはリモートの URL から、入れ子になった子の内容を読み込むことができるように RootElement
を拡張します。
JsonElement
は、2 つの形式でインスタンス化できる RootElement
です。 一方のバージョンでは、オンデマンドでコンテンツを読み込む RootElement
が作成されます。 これらは JsonElement
コンストラクターを使用して作成され、末尾で追加の引数 (コンテンツを読み込む URL) を受け取ります。
var je = new JsonElement ("Dynamic Data", "https://tirania.org/tmp/demo.json");
もう一方の形式では、ローカル ファイルまたは既に解析済みの既存の System.Json.JsonObject
ファイルからデータが作成されます。
var je = JsonElement.FromFile ("json.sample");
using (var reader = File.OpenRead ("json.sample"))
return JsonElement.FromJson (JsonObject.Load (reader) as JsonObject, arg);
MT.D での JSON の使用の詳細については、JSON 要素のチュートリアルを参照してください。
その他の機能
プルして更新のサポート
pull-to- Refresh は、 Tweetie2 アプリで最初に見つかった視覚効果であり、多くのアプリケーションで一般的な効果となりました。
プルして更新のサポートをダイアログに追加するには、次の 2 つのことを行う必要があります。ユーザーがデータをプルしたときに通知を受け取るイベント ハンドラーをフックし、データが読み込まれて既定の状態に戻すときに DialogViewController
に通知します。
通知のフックは簡単です。次のように、DialogViewController
の RefreshRequested
イベントに接続するだけです。
dvc.RefreshRequested += OnUserRequestedRefresh;
その後、メソッド OnUserRequestedRefresh
で、データの読み込みをキューに入れたり、ネットからのデータを要求したり、スレッドをスピンしてデータを計算したりします。 データが読み込まれたら、新しいデータが存在することを DialogViewController
に通知する必要があります。ビューを既定の状態に復元するには、ReloadComplete
を呼び出します。
dvc.ReloadComplete ();
検索サポート
検索をサポートするには、DialogViewController
で EnableSearch
プロパティを設定します。 検索バーで透かしテキストとして使用するために、SearchPlaceholder
プロパティを設定することもできます。
ユーザーが入力すると、検索によりビューの内容が変わります。 表示可能なフィールドを検索し、ユーザーに表示します。 DialogViewController
は、結果に対して新しいフィルター操作をプログラムによって開始、終了、またはトリガーするための 3 つのメソッドを公開しています。 これらのメソッドを以下に示します。
StartSearch
FinishSearch
PerformFilter
システムは拡張可能であるため、必要に応じてこの動作を変更できます。
バックグラウンドでの画像の読み込み
MonoTouch.Dialog には、TweetStation アプリケーションのイメージ ローダーが組み込まれています。 バックグラウンドでの画像の読み込み、キャッシュのサポートにこのイメージ ローダーを使用でき、画像が読み込まれたときにコードに通知できます。
また、これによって送信ネットワーク接続の数も制限されます。
イメージ ローダーは ImageLoader
クラスに実装されています。必要なのは DefaultRequestImage
メソッドを呼び出すことだけです。、読み込む画像の URI と、画像が読み込まれたときに呼び出される IImageUpdated
インターフェイスのインスタンスを指定する必要があります。
たとえば、次のコードは URL から BadgeElement
に画像を読み込みます。
string uriString = "http://some-server.com/some image url";
var rootElement = new RootElement("Image Loader") {
new Section() {
new BadgeElement( ImageLoader.DefaultRequestImage( new Uri(uriString), this), "Xamarin")
}
};
ImageLoader クラスでは Purge メソッドを公開しています。これは、現在メモリにキャッシュされているすべての画像を解放するときに呼び出すことができます。 現在のコードには、50 枚分の画像のキャッシュがあります。 別のキャッシュ サイズを使用する場合 (たとえば、画像が大きすぎて 50 枚の画像では多すぎると予想される場合)、ImageLoader のインスタンスを作成し、キャッシュに保持する画像の数を渡します。
LINQ を使用して要素の階層を作成する
LINQ と C# の初期化構文をうまく使って、LINQ を使用して要素の階層を作成できます。 たとえば、次のコードは、いくつかの文字列配列から画面を作成し、各 StringElement
に渡される匿名関数を使用してセルの選択を処理します。
var rootElement = new RootElement ("LINQ root element") {
from x in new string [] { "one", "two", "three" }
select new Section (x) {
from y in "Hello:World".Split (':')
select (Element) new StringElement (y, delegate { Debug.WriteLine("cell tapped"); })
}
};
これを XML データ ストアまたはデータベースのデータと簡単に組み合わせて、データからほぼ完全に複雑なアプリケーションを作成できます。
MT.D の拡張
カスタム要素の作成
既存の要素から継承するか、ルート クラス Element から派生することで、独自の Element を作成できます。
独自の Element を作成するには、次のメソッドをオーバーライドします。
// To release any heavy resources that you might have
void Dispose (bool disposing);
// To retrieve the UITableViewCell for your element
// you would need to prepare the cell to be reused, in the
// same way that UITableView expects reusable cells to work
UITableViewCell GetCell (UITableView tv);
// To retrieve a "summary" that can be used with
// a root element to render a summary one level up.
string Summary ();
// To detect when the user has tapped on the cell
void Selected (DialogViewController dvc, UITableView tableView, NSIndexPath path);
// If you support search, to probe if the cell matches the user input
bool Matches (string text);
要素に可変サイズを設定できる場合は、次の 1 つのメソッドを含む IElementSizing
インターフェイスを実装する必要があります。
// Returns the height for the cell at indexPath.Section, indexPath.Row
float GetHeight (UITableView tableView, NSIndexPath indexPath);
base.GetCell(tv)
を呼び出して返されたセルをカスタマイズすることで GetCell
メソッドを実装することを計画している場合は、次のように、Element に固有のキーを返すように CellKey
プロパティをオーバーライドする必要もあります。
static NSString MyKey = new NSString ("MyKey");
protected override NSString CellKey {
get {
return MyKey;
}
}
これはほとんどの要素で機能しますが、さまざまなレンダリング シナリオで独自のキーセットが使用されるため、StringElement
と StyledStringElement
では機能しません。 これらのクラスではコードをレプリケートする必要があります。
DialogViewControllers (DVC)
Reflection と Elements API の両方で同じ DialogViewController
が使用されます。 ビューの外観をカスタマイズする場合や、UI の基本的な作成を超える UITableViewController
の機能を使用することもできます。
DialogViewController
は単なる UITableViewController
のサブクラスであり、UITableViewController
をカスタマイズするのと同じ方法でカスタマイズできます。
たとえば、リスト スタイルを Grouped
または Plain
のいずれかに変更する場合は、次のようにコントローラーを作成するときにプロパティを変更して、この値を設定できます。
var myController = new DialogViewController (root, true) {
Style = UITableViewStyle.Grouped;
}
背景の設定など、より高度な DialogViewController
のカスタマイズを行うには、次の例に示すようにそれをサブクラス化し、適切なメソッドをオーバーライドします。
class SpiffyDialogViewController : DialogViewController {
UIImage image;
public SpiffyDialogViewController (RootElement root, bool pushing, UIImage image)
: base (root, pushing)
{
this.image = image;
}
public override LoadView ()
{
base.LoadView ();
var color = UIColor.FromPatternImage(image);
TableView.BackgroundColor = UIColor.Clear;
ParentViewController.View.BackgroundColor = color;
}
}
もう 1 つのカスタマイズのポイントは、DialogViewController
にある次の仮想メソッドです。
public override Source CreateSizingSource (bool unevenRows)
このメソッドは、セルのサイズが均等な場合は DialogViewController.Source
のサブクラスを返し、セルが不均等な場合は DialogViewController.SizingSource
のサブクラスを返す必要があります。
このオーバーライドを使用して、任意の UITableViewSource
のメソッドをキャプチャできます。 たとえば、TweetStation ではこれを使用して、ユーザーが一番上までスクロールしたタイミングを追跡し、未読ツイートの数に応じて更新しています。
検証
要素では検証自体を提供しません。Web ページやデスクトップ アプリケーションに適したモデルは iPhone のインタラクション モデルに直接マップされないためです。
データの検証を行う場合は、入力されたデータを使用してユーザーがアクションをトリガーするときにこれを行う必要があります。 たとえば、ツールバーの上部の [完了] または [次へ] ボタン、または次のステージに進むボタンとして使用される StringElement
などです。
ここでは、基本的な入力検証を実行し、ユーザーとパスワードの組み合わせとサーバーの有効性をチェックするなどの、より複雑な検証を実行します。
エラーをユーザーに通知する方法は、アプリケーション固有です。 UIAlertView
をポップアップ表示したり、ヒントを表示したりすることもできます。
まとめ
この記事では、MonoTouch.Dialog に関する多くの情報について説明しました。 MT.D のしくみの基礎についてと、MT.D を構成するさまざまなコンポーネントについて説明しました。 また、MT.D でサポートされているさまざまな要素とテーブルのカスタマイズについても示し、MT.D をカスタム要素で拡張する方法について説明しました。 さらに、JSON から要素を動的に作成できる、MT.D での JSON のサポートについて説明しました。