在 Xamarin.iOS 中使用数据填充表

要将行添加到 UITableView,需要实现 UITableViewSource 子类并替代表视图为填充自身所调用的方法。

本指南涵盖:

  • 子类化 UITableViewSource
  • 单元格重用
  • 添加索引
  • 添加页眉和页脚

子类化 UITableViewSource

UITableViewSource 子类会分配给每个 UITableView。 表视图会查询源类以确定如何呈现自身,例如,需要多少行,以及每行的高度(如果与默认值不同)。 最重要的是,源提供了使用数据填充的每个单元格视图。

为了让表格显示数据,只需要实现两个必需的方法:

  • 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 可以使用任何数据结构,从简单的字符串数组(如此示例所示)到列表 <> 或其他集合。 实现 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);
}

生成的表如下所示:

Sample table running

大多数表格允许用户触摸某一行以选择它并执行一些其他操作(例如播放歌曲、呼叫联系人或显示另一个屏幕)。 为了实现这一点,我们需要执行几个操作。 我首先创建一个 AlertController,以在用户单击行时显示一条消息,方法是将以下内容添加到 RowSelected 方法:

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 类以传递 this 引用的 ViewDidLoad 方法:

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

最后,返回到 RowSelected 方法,在缓存字段上调用 PresentViewController

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

    ...
}

现在,用户可以触摸某一行,此时会显示警报:

The row selected alert

单元格重用

此示例只有六个项目,因此不需要重用单元格。 但在显示数百或数千行时,如果屏幕一次只能容纳几行,则创建数百或数千个 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 可有效地为不同类型的单元格创建单独的队列。 在此示例中,所有单元格外观相同,因此只使用一个硬编码标识符。 如果存在不同类型的单元格,则每个单元格应在实例化时以及从重用队列请求它们时具有不同的标识符字符串。

iOS 6+ 中的单元格重用

iOS 6 添加了类似于集合视图简介的单元格重用模式。 尽管上述现有重用模式仍支持向后兼容性,但这种新模式是可取的,因为它不需要对单元格执行 null 检查。

使用新模式时,应用程序会注册要通过调用控制器构造函数中的 RegisterClassForCellReuseRegisterNibForCellReuse 来使用的单元格类或 xib。 然后,在 GetCell 方法中取消单元格排队时,只需调用 DequeueReusableCell 来传递为单元格类或 xib 注册的标识符以及索引路径。

例如,以下代码会在 UITableViewController 中注册自定义单元格类:

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

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

注册 MyCell 类后,可以在不需要进行额外 null 检查的情况下在 UITableViewSourceGetCell 方法中取消单元格排队,如下所示:

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 示例可从文件中加载更长的项列表来演示索引。 索引中的每个项对应于表的“部分”。

The Index display

要支持“部分”,需要对表后面的数据进行分组,因此 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 - 此方法是可选的,默认情况下,表假定具有一个部分。 当显示索引时,此方法应返回索引中的项数(例如,如果索引包含英文字母的所有字母,则返回 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<> 的效果非常好。 本示例不会使用字母表对单元格进行分组,而是按植物类型对蔬菜进行分组。 输出如下所示:

Sample Headers and Footers

要显示页眉和页脚,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";
}

可以对 UITableViewSource 使用 GetViewForHeaderGetViewForFooter 方法替代通过视图对象来进一步自定义页眉和页脚的外观。