2016-09-16 149 views
0

我是Xamarin的新手,试图制作一些基本的todolist iOS应用程序。如何在删除TableView中的行后更新我的indexPath?

经过一段时间与TableViews挣扎后,我现在有一个TableSource和自定义单元格包含UISwitch

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

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被调用。这里是我的tablesource:

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); 
    } 
} 

基本上,DeleteTodoHandler被称为每当在UISwitch用户点击。它首先正确删除该单元格。但是,indexPath从不更新。

我的意思是:想象一下,我的TodoList中有3个单元格。

小区1 - > indexPath.Row = 0 小区2 - > indexPath.Row = 1 小区3 - > indexPath.Row = 2

如果删除第二个,小区2,从indexPath Cell3应该= 1而不是2,但情况并非如此。这意味着,如果我尝试之后删除小区3,

TableItems.RemoveAt(indexPath.Row) 

将尝试从正带领着我们一个例外,因为只有2列表中的项目列表中删除的项目3。

我在Xamarin Doc中找到了用于删除TableView中的行的示例,但在我的情况下显然不起作用。 indexPath.Row是只读的,我可以在删除TableView中的项目后正确更新indexPath吗?

回答

0

您应该使用包含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(); 
      }; 
     } 
    } 

希望它能帮助你。

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

------------------------------ New --------------- ---------------------

我只是建议你将所有的逻辑代码移入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”在您的删除事件处理程序中计算索引,就像我上面提到的那样。它将始终得到正确的索引。

-------------------关于您的问题--------------------

你发的关于删除事件的init的错误,你必须初始化它的细胞构造的时候,像我一样:

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]; 

就会造成重复绑定问题,因为您的表格单元格被重用,所以当您尝试刷新/插入/删除甚至sc时滚动您的UITableView,将触发“GetCell”方法,因此当您尝试调用单元格的删除事件时,会调用多个代理,第一个代理正在工作,并且如果您至少有两个代理,则会导致问题你遇到了。

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

因为有2个代表,所以它抛出一个异常。

最后,我已经将完整的示例代码上传到我的GitHub,如果需要,请下载它。

UITalbViewManagement Sample code

+0

嗨,所有的 首先,感谢你的答案。我从来没有看到过这个角度的问题,现在看来确实更合乎逻辑。 但是,即使我肯定会进入TableSource中的DeleteCell(由Console.WriteLine确认),但我绝对不会通过控制器的ViewDidLoad函数中的DeleteCell。 你知道为什么吗? – Pictar

+0

我修改了我的答案,它应该可以解决您的问题。@ Pictar –

+0

删除工作正常,感谢您的帮助:)唯一剩下的问题是添加了Todo。我为ViewDidLoad中的添加创建了视图,并正确捕获了添加的事件。当我启动应用程序并添加一个Todo时,单元格显示正确,但是当我尝试删除它之后,我得到** ** NullException ** ** dIndexPath ** .. – Pictar

相关问题