Xamarin.iOS でのテーブルへのデータの設定する

UITableView に行を追加するには、UITableViewSource サブクラスを実装し、テーブル ビューがそれ自体を設定するために呼び出すメソッドをオーバーライドする必要があります。

このガイドでは、次のことを説明します。

  • UITableViewSource のサブクラス化
  • セルの再利用
  • インデックスの追加
  • ヘッダーとフッターの追加

UITableViewSource のサブクラス化

UITableViewSource サブクラスは、すべての UITableView に割り当てられます。 テーブル ビューは、ソース クラスのクエリを実行して、それ自体をレンダリングする方法を決定します (たとえば、必要な行の数や、既定値と異なる場合の各行の高さ)。 最も重要なのは、ソースによって各セル ビューにデータが設定されることです。

テーブルにデータを表示するために必要なのは、2 つの必須メソッドだけです。

  • RowsInSection: テーブルに表示する必要がある合計データ行数の nint カウントを返します。
  • GetCell: メソッドに渡された行インデックスに対応する、データが設定された UITableViewCell を返します。

BasicTable サンプルのファイル TableSource.cs には、UITableViewSource で可能な最も簡単な実装があります。 以下のコード スニペットを見ると、テーブルに表示する文字列の配列を受け取り、各文字列を含む既定のセル スタイルを返すことがわかります。

public class TableSource : UITableViewSource {

        string[] TableItems;
        string CellIdentifier = "TableCell";

        public TableSource (string[] items)
        {
            TableItems = items;
        }

        public override nint RowsInSection (UITableView tableview, nint section)
        {
            return TableItems.Length;
        }

        public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
        {
            UITableViewCell cell = tableView.DequeueReusableCell (CellIdentifier);
            string item = TableItems[indexPath.Row];

            //if there are no cells to reuse, create a new one
            if (cell == null)
            {
                cell = new UITableViewCell (UITableViewCellStyle.Default, CellIdentifier);
            }

            cell.TextLabel.Text = item;

            return cell;
        }
}

UITableViewSource では、単純な文字列配列 (この例で示すようなもの) から List <> や他のコレクションまで、任意のデータ構造を使用できます。 UITableViewSource メソッドの実装により、テーブルが基になるデータ構造から分離されます。

このサブクラスを使うには、文字列配列を作成してソースを構築してから、それを UITableView のインスタンスに割り当てます。

public override void ViewDidLoad ()
{
    base.ViewDidLoad ();
    table = new UITableView(View.Bounds); // defaults to Plain style
    string[] tableItems = new string[] {"Vegetables","Fruits","Flower Buds","Legumes","Bulbs","Tubers"};
    table.Source = new TableSource(tableItems);
    Add (table);
}

結果のテーブルは次のようになります。

サンプル テーブルの実行

ほとんどのテーブルでは、ユーザーは行をタッチして選び、他の何らかのアクション (曲の再生、連絡先の呼び出し、別の画面の表示など) を実行できます。 これを実現するには、いくつかのことを行う必要があります。 最初に、RowSelected メソッドに以下を追加し、ユーザーが行をクリックしたときにメッセージを表示する AlertController を作成します。

public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
    UIAlertController okAlertController = UIAlertController.Create ("Row Selected", tableItems[indexPath.Row], UIAlertControllerStyle.Alert);
    okAlertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
    ...

    tableView.DeselectRow (indexPath, true);
}

次に、ビュー コントローラーのインスタンスを作成します。

HomeScreen owner;

ビュー コントローラーをパラメータとして受け取ってフィールドに保存するコンストラクターを UITableViewSource クラスに追加します。

public TableSource (string[] items, HomeScreen owner)
{
    ...
    this.owner = owner;

}

UITableViewSource クラスが作成される ViewDidLoad メソッドを変更して、this 参照を渡します。

table.Source = new TableSource(tableItems, this);

最後に、RowSelected メソッドに戻り、キャッシュされたフィールドで PresentViewController を呼び出します。

public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
    ...
    owner.PresentViewController (okAlertController, true, null);

    ...
}

これで、ユーザーは行にタッチでき、アラートが表示されます。

行選択のアラート

セルの再利用

この例では項目が 6 つしかないため、セルの再利用は必要ありません。 ただし、数百行や数千行を表示するときは、一度に数行しか画面に収まらない場合に、数百または数千の UITableViewCell オブジェクトを作成するのは、メモリの無駄になります。

この状況を回避するため、画面に表示されなくなったセルのビューは、再利用のためにキューに配置されます。 ユーザーがスクロールすると、テーブルは GetCell を呼び出して表示する新しいビューを要求します。(現在表示されていない) 既存のセルを再利用するには、DequeueReusableCell メソッドを呼び出すだけです。 再利用できるセルがある場合はそれが返されます。ない場合は null が返され、コードで新しいセル インスタンスを作成する必要があります。

次のコード スニペットの例は、そのパターンを示しています。

// request a recycled cell to save memory
UITableViewCell cell = tableView.DequeueReusableCell (cellIdentifier);
// if there are no cells to reuse, create a new one
if (cell == null)
    cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier);

cellIdentifier は、異なる種類のセルに対して個別のキューを効果的に作成します。 この例では、すべてのセルが同じように見えるので、ただ 1 つのハードコーディングされた識別子が使われます。 異なる種類のセルがある場合は、インスタンス化されるときと、再利用キューから要求されるときの両方で、それぞれ異なる識別子文字列を持つ必要があります。

iOS 6 以降でのセルの再利用

iOS 6 では、コレクション ビューで導入されたものと似たセル再利用パターンが追加されました。 上で示した既存の再利用パターンは、下位互換性のためにまだサポートされていますが、セルでの null チェックの必要がなくなるこの新しいパターンをお勧めします。

新しいパターンでは、アプリケーションは、コントローラーのコンストラクターで RegisterClassForCellReuse または RegisterNibForCellReuse を呼び出して、使用するセル クラスまたは xib を登録します。 次に、GetCell メソッドでセルをデキューするときは、DequeueReusableCell を呼び出して、セル クラスまたは xib に登録した識別子とインデックス パスを渡すだけです。

たとえば、次のコードは、UITableViewController にカスタム セル クラスを登録します。

public class MyTableViewController : UITableViewController
{
  static NSString MyCellId = new NSString ("MyCellId");

  public MyTableViewController ()
  {
    TableView.RegisterClassForCellReuse (typeof(MyCell), MyCellId);
  }
  ...
}

MyCell クラスを登録すると、次に示すように、UITableViewSourceGetCell メソッドでセルをデキューでき、追加の null チェックは必要ありません。

class MyTableSource : UITableViewSource
{
  public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
  {
    // if cell is not available in reuse pool, iOS will create one automatically
    // no need to do null check and create cell manually
    var cell = (MyCell) tableView.DequeueReusableCell (MyCellId, indexPath);

    // do whatever you need to with cell, such as assigning properties, etc.

    return cell;
  }
}

カスタム セル クラスで新しい再利用パターンを使う場合は、次のスニペットで示すように、IntPtr を受け取るコンストラクターを実装する必要があることに注意してください。そうしないと、Objective-C はセル クラスのインスタンスを構築できません。

public class MyCell : UITableViewCell
{
  public MyCell (IntPtr p):base(p)
  {
  }
  ...
}

上で説明したトピックの例は、この記事にリンクされている BasicTable サンプルで確認できます。

インデックスの追加

インデックスは、通常はアルファベット順に並んでいますが、任意の条件でインデックスを作成できる長いリストを、ユーザーがスクロールするのに役立ちます。 BasicTableIndex サンプルでは、インデックスについて示すために、ファイルから項目の長いリストを読み込みます。 インデックス内の各項目は、テーブルの 'セクション' に対応します。

インデックスの表示

'セクション' をサポートするには、テーブルの背後にあるデータをグループ化する必要があるため、BasicTableIndex サンプルでは、辞書のキーとして各項目の最初の文字を使って、文字列の配列から Dictionary<> を作成します。

indexedTableItems = new Dictionary<string, List<string>>();
foreach (var t in items) {
    if (indexedTableItems.ContainsKey (t[0].ToString ())) {
        indexedTableItems[t[0].ToString ()].Add(t);
    } else {
        indexedTableItems.Add (t[0].ToString (), new List<string>() {t});
    }
}
keys = indexedTableItems.Keys.ToArray ();

その後、UITableViewSource サブクラスでは、Dictionary<> を使うために次のメソッドを追加または変更する必要があります。

  • NumberOfSections: このメソッドはオプションであり、既定では、テーブルのセクションは 1 つと想定されています。 インデックスを表示する場合は、このメソッドでインデックス内の項目の数を返す必要があります (たとえば、インデックスに英語のアルファベットの文字がすべて含まれている場合は 26)。
  • RowsInSection: 指定されたセクションの行数を返します。
  • SectionIndexTitles: インデックスの表示に使われる文字列の配列を返します。 サンプル コードでは、文字の配列を返しています。

サンプル ファイル BasicTableIndex/TableSource.cs 内の更新されたメソッドは次のようになります。

public override nint NumberOfSections (UITableView tableView)
{
    return keys.Length;
}
public override nint RowsInSection (UITableView tableview, nint section)
{
    return indexedTableItems[keys[section]].Count;
}
public override string[] SectionIndexTitles (UITableView tableView)
{
    return keys;
}

インデックスは通常、プレーン テーブル スタイルでのみ使われます。

ヘッダーとフッターの追加

テーブル内の行を視覚的にグループ化するには、ヘッダーとフッターを使用できます。 必要なデータ構造はインデックスを追加するのとよく似ており、Dictionary<> がまったく問題なく機能します。 この例では、アルファベットを使ってセルをグループ化する代わりに、植物の種類別に野菜をグループ化します。 出力は次のようになります。

ヘッダーとフッターのサンプル

ヘッダーとフッターを表示するには、UITableViewSource サブクラスに次の追加メソッドが必要です。

  • TitleForHeader: ヘッダーとして使うテキストを返します
  • TitleForFooter: フッターとして使うテキストを返します。

サンプル ファイル BasicTableHeaderFooter/Code/TableSource.cs 内の更新されたメソッドは次のようになります。

public override string TitleForHeader (UITableView tableView, nint section)
{
    return keys[section];
}
public override string TitleForFooter (UITableView tableView, nint section)
{
    return indexedTableItems[keys[section]].Count + " items";
}

ビュー オブジェクトで UITableViewSourceGetViewForHeaderGetViewForFooter メソッドのオーバーライドを使って、ヘッダーとフッターの外観をさらにカスタマイズできます。