使用 Xamarin.iOS 编辑表

表编辑功能是通过替代 UITableViewSource 子类中的方法启用的。 最简单的编辑行为是滑动删除手势,可以通过单个方法替代来实现。 在编辑模式下,可以对表进行更复杂的编辑(包括移动行)。

滑动删除

滑动删除功能是用户期望在 iOS 中实现的一种自然手势。

轻扫以删除的示例

有三种方法替代会影响在单元格中显示“删除”按钮的滑动手势:

  • CommitEditingStyle – 表来源检测此方法是否已被替代并自动启用滑动删除手势。 此方法的实现应当在 UITableView 上调用 DeleteRows 以导致单元格消失,并从模型中移除基础数据(例如数组、字典或数据库)。
  • CanEditRow – 如果 CommitEditingStyle 被替代,则会假定所有行都是可编辑的。 如果实现了此方法并且它返回 false(对于某些特定行或所有行),则滑动删除手势在该单元格中将不可用。
  • TitleForDeleteConfirmation –(可选)指定“删除”按钮的文本。 如果未实现此方法,按钮文本将为“删除”。

这些方法是在 TableSource 类中实现的,如下所示:

public override void CommitEditingStyle (UITableView tableView, UITableViewCellEditingStyle editingStyle, Foundation.NSIndexPath indexPath)
{
    switch (editingStyle) {
        case UITableViewCellEditingStyle.Delete:
            // remove the item from the underlying data source
            tableItems.RemoveAt(indexPath.Row);
            // delete the row from the table
            tableView.DeleteRows (new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Fade);
            break;
        case UITableViewCellEditingStyle.None:
            Console.WriteLine ("CommitEditingStyle:None called");
            break;
    }
}
public override bool CanEditRow (UITableView tableView, NSIndexPath indexPath)
{
    return true; // return false if you wish to disable editing for a specific indexPath or for all rows
}
public override string TitleForDeleteConfirmation (UITableView tableView, NSIndexPath indexPath)
{   // Optional - default text is 'Delete'
    return "Trash (" + tableItems[indexPath.Row].SubHeading + ")";
}

对于此示例,UITableViewSource 已更新为使用 List<TableItem>(而非字符串数组)作为数据源,因为它支持在集合中添加和删除项。

编辑模式

当表处于编辑模式时,用户会在每个行上看到红色的“停止”小组件,它在被触控时将显示“删除”按钮。 此表还会显示一个“图柄”图标,指示可以拖动行来更改顺序。 TableEditMode 示例实现了这些功能,如下所示。

TableEditMode 示例实现了这些功能,如下所示

UITableViewSource 上有许多不同的方法会影响表的编辑模式行为:

  • CanEditRow – 是否可以编辑每行。 返回 false 将阻止在编辑模式下进行滑动删除和删除。
  • CanMoveRow – 返回 true 将启用移动“图柄”,返回 false 将阻止移动。
  • EditingStyleForRow – 当表处于编辑模式时,此方法的返回值决定了单元格是显示红色删除图标还是绿色添加图标。 如果行不应当可编辑,则返回 UITableViewCellEditingStyle.None
  • MoveRow - 在行被移动时调用,以便修改基础数据结构以匹配表中显示的数据。

前三个方法的实现相对直截了当 -- 除非你想要使用 indexPath 更改特定行的行为,否则只需对整个表的返回值进行硬编码。

public override bool CanEditRow (UITableView tableView, NSIndexPath indexPath)
{
    return true; // return false if you wish to disable editing for a specific indexPath or for all rows
}
public override bool CanMoveRow (UITableView tableView, NSIndexPath indexPath)
{
    return true; // return false if you don't allow re-ordering
}
public override UITableViewCellEditingStyle EditingStyleForRow (UITableView tableView, NSIndexPath indexPath)
{
    return UITableViewCellEditingStyle.Delete; // this example doesn't support Insert
}

MoveRow 实现稍微复杂一点,因为它需要更改基础数据结构以匹配新顺序。 因为数据是以 List 形式实现的,所以,下面的代码将数据从其旧位置删除,并将其插入新位置。 例如,如果数据存储在具有“顺序”列的 SQLite 数据库表中,则此方法需要执行一些 SQL 操作来将该列中的数字重新排序。

public override void MoveRow (UITableView tableView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
    var item = tableItems[sourceIndexPath.Row];
    var deleteAt = sourceIndexPath.Row;
    var insertAt = destinationIndexPath.Row;

    // are we inserting
    if (destinationIndexPath.Row < sourceIndexPath.Row) {
        // add one to where we delete, because we're increasing the index by inserting
        deleteAt += 1;
    } else {
        // add one to where we insert, because we haven't deleted the original yet
        insertAt += 1;
    }
    tableItems.Insert (insertAt, item);
    tableItems.RemoveAt (deleteAt);
}

最后,若要使表进入编辑模式,则“编辑”按钮需要调用 SetEditing,如下所示:

table.SetEditing (true, true);

当用户完成编辑后,“完成”按钮应关闭编辑模式:

table.SetEditing (false, true);

行插入编辑样式

表中的行插入不是一个常见的用户界面 - 标准 iOS 应用中的主要示例是“编辑联系人”屏幕。 此屏幕截图显示了行插入功能的工作原理 - 在编辑模式下,有一个额外的行(在被单击时)用于将额外的行插入到数据中。 编辑完成后,临时的“(新增)”行将被移除。

编辑完成后,临时的“新增”行被移除

UITableViewSource 上有许多不同的方法会影响表的编辑模式行为。 这些方法在示例代码中实现如下:

  • EditingStyleForRow – 对于包含数据的行,将返回 UITableViewCellEditingStyle.Delete;对于最后一行,将返回 UITableViewCellEditingStyle.Insert(将专门添加以充当插入按钮)。
  • CustomizeMoveTarget – 当用户移动单元格时,此可选方法的返回值可以替代用户选择的位置。 这意味着你可以阻止用户“删除”某些位置中的单元格,例如,此示例阻止任何行被移动到“(新增)”行之后。
  • CanMoveRow – 返回 true 将启用移动“图柄”,返回 false 将阻止移动。 在此示例中,最后一行隐藏了移动“图柄”,因为它仅用于充当插入按钮。

我们还添加了两个自定义方法来添加“插入”行,然后在不再需要时再次移除它。 它们是从“编辑”和“完成”按钮调用的

  • WillBeginTableEditing – 当“编辑”按钮被触控时,它将调用 SetEditing 来将表置于编辑模式。 这会触发 WillBeginTableEditing 方法,在表末尾显示“(新增)”行以充当“插入按钮”。
  • DidFinishTableEditing – 触控“完成”按钮时将再次调用 SetEditing 以关闭编辑模式。 不再需要编辑时,示例代码会从表中移除“(新增)”行。

示例文件 TableEditModeAdd/Code/TableSource.cs 中实现了这些方法替代:

public override UITableViewCellEditingStyle EditingStyleForRow (UITableView tableView, NSIndexPath indexPath)
{
    if (tableView.Editing) {
        if (indexPath.Row == tableView.NumberOfRowsInSection (0) - 1)
            return UITableViewCellEditingStyle.Insert;
        else
            return UITableViewCellEditingStyle.Delete;
    } else // not in editing mode, enable swipe-to-delete for all rows
        return UITableViewCellEditingStyle.Delete;
}
public override NSIndexPath CustomizeMoveTarget (UITableView tableView, NSIndexPath sourceIndexPath, NSIndexPath proposedIndexPath)
{
    var numRows = tableView.NumberOfRowsInSection (0) - 1; // less the (add new) one
    if (proposedIndexPath.Row >= numRows)
        return NSIndexPath.FromRowSection(numRows - 1, 0);
    else
        return proposedIndexPath;
}
public override bool CanMoveRow (UITableView tableView, NSIndexPath indexPath)
{
    return indexPath.Row < tableView.NumberOfRowsInSection (0) - 1;
}

这两个自定义方法用于在表的编辑模式被启用或禁用时添加和移除“(新增)”行

public void WillBeginTableEditing (UITableView tableView)
{
    tableView.BeginUpdates ();
    // insert the 'ADD NEW' row at the end of table display
    tableView.InsertRows (new NSIndexPath[] {
            NSIndexPath.FromRowSection (tableView.NumberOfRowsInSection (0), 0)
        }, UITableViewRowAnimation.Fade);
    // create a new item and add it to our underlying data (it is not intended to be permanent)
    tableItems.Add (new TableItem ("(add new)"));
    tableView.EndUpdates (); // applies the changes
}
public void DidFinishTableEditing (UITableView tableView)
{
    tableView.BeginUpdates ();
    // remove our 'ADD NEW' row from the underlying data
    tableItems.RemoveAt ((int)tableView.NumberOfRowsInSection (0) - 1); // zero based :)
    // remove the row from the table display
    tableView.DeleteRows (new NSIndexPath[] { NSIndexPath.FromRowSection (tableView.NumberOfRowsInSection (0) - 1, 0) }, UITableViewRowAnimation.Fade);
    tableView.EndUpdates (); // applies the changes
}

最后,此代码实例化“编辑”和“完成”按钮,其中 lambda 在被触控时启用或禁用编辑模式:

done = new UIBarButtonItem(UIBarButtonSystemItem.Done, (s,e)=>{
    table.SetEditing (false, true);
    NavigationItem.RightBarButtonItem = edit;
    tableSource.DidFinishTableEditing(table);
});
edit = new UIBarButtonItem(UIBarButtonSystemItem.Edit, (s,e)=>{
    if (table.Editing)
        table.SetEditing (false, true); // if we've half-swiped a row
    tableSource.WillBeginTableEditing(table);
    table.SetEditing (true, true);
    NavigationItem.LeftBarButtonItem = null;
    NavigationItem.RightBarButtonItem = done;
});

此行插入 UI 模式不经常使用,不过,你也可以使用 UITableView.BeginUpdatesEndUpdates 方法对任何表中的单元格的插入或移除进行动画处理。 使用这些方法的规则是,RowsInSection 返回的值在 BeginUpdatesEndUpdates 调用之间的差额必须与通过 InsertRowsDeleteRows 方法添加/删除的单元格的净数目匹配。 如果未更改基础数据源来匹配表视图中的插入/删除,将会发生错误。