删除 TableView 中的行后如何更新 indexPath?

Posted

技术标签:

【中文标题】删除 TableView 中的行后如何更新 indexPath?【英文标题】:How can I update my indexPath after deleting a Row in a TableView? 【发布时间】:2016-09-16 16:55:32 【问题描述】:

我是 Xamarin 的新手,正在尝试制作一些基本的 todolist ios 应用程序。

在使用TableViews 挣扎了一段时间后,我现在有了一个TableSource,以及包含UISwitch 的自定义单元格。

我的目标是,当用户激活UISwitch 时,TableView 的单元格被删除。为此,我在自定义单元格中创建了一些自定义事件。

public partial class TodoTableViewCell : UITableViewCell

    public delegate void DeleteTodoEventHandler(UITableView tableView, NSIndexPath indexPath);
    public event DeleteTodoEventHandler DeleteTodo;

    partial void DeleteTodoEvent(UISwitch sender)
    
        if (DeleteTodo != null)
            DeleteTodo(null, new NSIndexPath());
    

    public TodoTableViewCell(IntPtr p):base(p)
    

    

    public TodoTableViewCell(string reuseIdentifier) : base(UITableViewCellStyle.Default, reuseIdentifier)
    

    

    public void UpdateCell(TodoItem Item)
    
        TodoLabel.Text = Item.Name;
        DateLabel.Text = Formating.formatDate(Item.Date);
        HourLabel.Text = Formating.formatTime(Item.Date);
    

DeleteTodoEvent 在用户激活UISwitch 时被调用。这是我的表源:

public class TableSource : UITableViewSource


    List<TodoItem> TableItems;
    string CellIdentifier = "TodoCell";

    ...

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

        //---- if there are no cells to reuse, create a new one
        if (cell == null)
         
            cell = new TodoTableViewCell(CellIdentifier);
        

        cell.DeleteTodo += (table, index) => DeleteTodoHandler(tableView, indexPath);

        cell.UpdateCell(item);

        return cell;
    

    ...

    private void DeleteTodoHandler(UITableView tableView, NSIndexPath indexPath)
    
        TableItems.RemoveAt(indexPath.Row);
        // delete the row from the table
        tableView.DeleteRows(new NSIndexPath[]  indexPath , UITableViewRowAnimation.Fade);
    

基本上,只要用户点击UISwitch,就会调用DeleteTodoHandler。它首先正确删除了单元格。但是,indexPath 永远不会更新。

我的意思是:假设我的 TodoList 中有 3 个单元格。

Cell1 -> indexPath.Row = 0 Cell2 -> indexPath.Row = 1 Cell3 -> indexPath.Row = 2

如果我删除第二个 Cell2,则 Cell3 的 indexPath 应该是 = 1 而不是 2。情况并非如此。这意味着如果我在那之后尝试删除 Cell3,

TableItems.RemoveAt(indexPath.Row) 

将尝试从列表中删除第 3 项,这会导致我们出现异常,因为列表中只有 2 项。

我正在按照我在 Xamarin Doc 中找到的示例删除 TableView 中的行,但在我的情况下,它显然不起作用。 indexPath.Row 是只读的,从 TableView 中删除项目后,我该怎么做才能正确更新 indexPath?

【问题讨论】:

【参考方案1】:

您应该使用包含 UITableView 和自定义 UITableSource 对象的 ViewController 来处理删除事件。

然后你可以通过 UITableView 中的方法获取你点击的正确行,如下所示:

Foundation.NSIndexPath indexPath = tableView.IndexPathForCell(cell); 

同时从您的数据列表和您的 UI 中删除该行的数据。

这是我给你写的示例代码,你看一下:

这是视图控制器:

public override void ViewDidLoad ()
        
            //Create a custom table source
            MyTableSource mySource = new MyTableSource ();

            //Create a UITableView
            UITableView tableView = new UITableView ();
            CGRect tbFrame = this.View.Bounds;
            tbFrame.Y += 20;
            tbFrame.Height -= 20;
            tableView.Frame = tbFrame;
            tableView.Source = mySource;
            tableView.RowHeight = 50;
            this.Add (tableView);

            //Handle delete event
            mySource.DeleteCell += (cell) => 
                //Get the correct row
                Foundation.NSIndexPath indexPath = tableView.IndexPathForCell(cell);
                //Remove data from source list
                mySource.RemoveAt(indexPath.Row);
                //Remove selected row from UI
                tableView.BeginUpdates();
                tableView.DeleteRows(new Foundation.NSIndexPath[]indexPath,UITableViewRowAnimation.Fade);
                tableView.EndUpdates();
            ;
        

这个 MyTableSource.cs:

public delegate void DeleteCellHandler(UITableViewCell cell);
        public event DeleteCellHandler DeleteCell;

        private string CellID = "MyTableCell";
        private List<string> tableItems;

        public MyTableSource ()
        
            tableItems = new List<string> ();
            for (int i = 0; i < 10; i++) 
                tableItems.Add ("Test Cell " + i.ToString ());
            
        

        public void RemoveAt(int row)
        
            tableItems.RemoveAt (row);
        

        #region implement from UITableViewSource

        public override nint RowsInSection (UITableView tableview, nint section)
        
            return tableItems.Count;
        

        public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
        
            MyTableCell cell = tableView.DequeueReusableCell (CellID) as MyTableCell;
            if (null == cell) 
                //Init custom cell
                cell = new MyTableCell (UITableViewCellStyle.Default, CellID);
                //Bind delete event
                cell.DeleteCell += delegate 
                    if(null != DeleteCell)
                        DeleteCell(cell);
                ;
            
            cell.Title = tableItems [indexPath.Row];
            return cell;
        

        #endregion

这是 MyTableCell.cs:

public class MyTableCell : UITableViewCell
    
        public delegate void DeleteCellHandler();
        public event DeleteCellHandler DeleteCell;

        UILabel lbTitle = new UILabel ();
        UIButton btnDelete = new UIButton (UIButtonType.System);

        public string Title
            get 
                return lbTitle.Text;
            
            set 
                lbTitle.Text = value;
            
        

        public MyTableCell (UITableViewCellStyle style, string reuseID) : base(style,reuseID)
        
            lbTitle.Text = "";
            lbTitle.Frame = new CoreGraphics.CGRect (0, 0, 100, 50);
            this.AddSubview (lbTitle);

            btnDelete.SetTitle ("Delete", UIControlState.Normal);
            btnDelete.Frame = new CoreGraphics.CGRect (120, 0, 50, 50);
            this.AddSubview (btnDelete);
            btnDelete.TouchUpInside += delegate 
                if(null != DeleteCell)
                    DeleteCell();
            ;
        
    

希望对你有帮助。

如果您仍然需要一些建议,请在此处给我留言。

------------------新---- --------------------

我只是建议你把所有的逻辑代码移到 ViewController 中,因为根据 MVC 模型,它应该在 Controller 层。

如果你想通过调用一些 API 或从本地数据库加载数据来管理你的数据,那会更方便。

当然你可以把你的删除事件放在你的 TableSource 中,只需要从控制器中删除事件并把它放在 TableSource 中,比如:

ViewController.cs:

public override void ViewDidLoad ()
        
            //Create a custom table source
            MyTableSource mySource = new MyTableSource ();

            //Create a UITableView
            UITableView tableView = new UITableView ();
            CGRect tbFrame = this.View.Bounds;
            tbFrame.Y += 20;
            tbFrame.Height -= 20;
            tableView.Frame = tbFrame;
            tableView.Source = mySource;
            tableView.RowHeight = 50;
            this.Add (tableView);

//          //Handle delete event
//          mySource.DeleteCell += (cell) => 
//              //Get the correct row
//              Foundation.NSIndexPath indexPath = tableView.IndexPathForCell(cell);
//              //Remove data from source list
//              mySource.RemoveAt(indexPath.Row);
//              //Remove selected row from UI
//              tableView.BeginUpdates();
//              tableView.DeleteRows(new Foundation.NSIndexPath[]indexPath,UITableViewRowAnimation.Fade);
//              tableView.EndUpdates();
//          ;
        

在 TableSource.cs 中,像这样更改 GetCell 方法:

public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
        
            MyTableCell cell = tableView.DequeueReusableCell (CellID) as MyTableCell;
            if (null == cell) 
                //Init custom cell
                cell = new MyTableCell (UITableViewCellStyle.Default, CellID);
                //Bind delete event
                cell.DeleteCell += delegate 
//                  if(null != DeleteCell)
//                      DeleteCell(cell);
                    //Get the correct row
                    Foundation.NSIndexPath dIndexPath = tableView.IndexPathForCell(cell);
                    int deleteRowIndex = dIndexPath.Row;
                    Console.WriteLine("deleteRowIndex = "+deleteRowIndex);
                    //Remove data from source list
                    RemoveAt(deleteRowIndex);
                    //Remove selected row from UI
                    tableView.BeginUpdates();
                    tableView.DeleteRows(new Foundation.NSIndexPath[]dIndexPath,UITableViewRowAnimation.Fade);
                    tableView.EndUpdates();
                ;
            
            cell.Title = tableItems [indexPath.Row];
            return cell;
        

您犯的唯一错误是您不应该在自定义单元格的删除事件句柄中添加任何参数。

因为当您从表格中删除一个单元格时,其他单元格将永远不会更新自己的 IndexPath。

例如,如果您有 3 行,而您删除了第二行,它会起作用。 但是当您尝试删除第三行时(现在是第二行,因为您只是删除了第二行),您会得到一个异常,即您没有 3 行或索引超出范围,因为此单元格的 IndexPath 是还是2(正确的是1)。

因此,您需要使用 UITableView 的方法“IndexPathForCell”计算删除事件处理程序中的索引,就像我上面提到的那样。它总是会得到正确的索引。

------------------关于你的新问题--------------------

你犯的错误是关于删除事件初始化,你必须在构建单元格时初始化它,就像我一样:

public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
        
            MyTableCell cell = tableView.DequeueReusableCell (CellID) as MyTableCell;
            if (null == cell) 
                cell = new MyTableCell (UITableViewCellStyle.Default, CellID);
                //Bind delete event
                cell.DeleteCell += delegate 
                    //Delete stuff            
                ;
            
            cell.Title = tableItems [indexPath.Row];
            return cell;
        

如果你只是像你一样使用它:

if (null == cell) 
    cell = new MyTableCell (UITableViewCellStyle.Default, CellID);

//Bind delete event
cell.DeleteCell += delegate 
    //Delete stuff            
;
cell.Title = tableItems [indexPath.Row];

这会导致重复绑定问题,因为你的表格单元格被重复使用了,所以当你尝试刷新/插入/删除甚至滚动你的 UITableView 时,会触发“GetCell”方法,所以当你尝试调用单元格的删除时事件,会调用多个委托,第一个是工作的,如果你有至少 2 个委托,就会导致你遇到的问题。

效果是这样的截图: 重现步骤: 1 为tableview添加一个新项目; 2 删除刚刚添加的项目; 3 再次添加新项目; 4 再次删除刚刚添加的项目;

因为有 2 个委托,所以抛出了异常。

最后,我已经把完整的示例代码上传到了我的 GitHub 上,需要的可以下载。

UITalbViewManagement Sample code

【讨论】:

您好,首先感谢您的回答。我从来没有见过这个角度的问题,现在它确实看起来更合乎逻辑。但是,即使我肯定会进入 TableSource 内的 DeleteCell(由 Console.WriteLine 确认),但我永远不会通过 Controller 的 ViewDidLoad 函数内的 DeleteCell。你知道为什么吗? 我修改了答案,应该可以解决你的问题。@Pictar 删除工作正在进行中,感谢您的帮助 :) 唯一剩下的问题是添加了 Todo。我正在为 ViewDidLoad 中的添加创建视图并正确捕获添加事件。当我启动应用程序并添加一个 Todo 时,单元格会正确显示,但是当我尝试在此之后删除它时,我在 dIndexPath .. 周围得到一个 NullException 可以在某处分享您的代码吗?我很乐意为您提供帮助。 Pastebin 这是一个链接,其中包含一些关于我的问题的解释和代码。感谢您的帮助

以上是关于删除 TableView 中的行后如何更新 indexPath?的主要内容,如果未能解决你的问题,请参考以下文章

更新/插入/删除表中的行后触发的触发器有问题

可编辑的网格 ExtJS,如何在网格上编辑行后更新数据库中的行

如何在swift中删除行后刷新tableview?

删除从部分删除的所有行后出现的 Tableview 部分

为啥更新数据库中的行后出现登录问题?

删除行后更新 indexPath 值