我是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);
}
}
当用户激活UISwitch
时,会调用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);
}
}
基本上,只要用户点击UISwitch
,就会调用 DeleteTodoHandler 。它首先正确删除了单元格。但是,indexPath永远不会更新。
我的意思是:想象一下,我的TodoList中有3个单元格。
Cell1 - &gt; indexPath.Row = 0 Cell2 - &gt; indexPath.Row = 1 Cell3 - &gt; indexPath.Row = 2
如果删除第二个Cell2,则Cell3中的indexPath应为= 1而不是2.事实并非如此。这意味着如果我在此之后尝试删除Cell3,
TableItems.RemoveAt(indexPath.Row)
将尝试从列表中删除项目3,这导致我们发生异常,因为列表中只有2个项目。
我按照我在Xamarin Doc中找到的示例来删除TableView中的行,但在我的情况下,它显然无法正常工作。 indexPath.Row是只读的,在从TableView中删除项目后,我该怎么做才能正确更新indexPath?
答案 0 :(得分:0)
您应该使用包含UITableView和自定义UITableSource对象的ViewController来处理删除事件。
然后你可以从UITableView获得你点击的正确行,如下所示:
Foundation.NSIndexPath indexPath = tableView.IndexPathForCell(cell);
同时从数据列表和用户界面中删除行的数据。
这是我为您编写的示例代码,请看一下:
这是ViewController:
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”计算你的删除事件处理程序中的索引,就像我上面提到的那样。它将始终获得正确的索引。
------------------关于你的新问题--------------------
你犯的错误是删除事件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];
这将导致重复的绑定问题,因为您的表格单元格被重用,所以当您尝试刷新/插入/删除甚至滚动UITableView时,将触发“GetCell”方法,因此当您尝试调用单元格的删除时事件,将调用多个委托,第一个是工作,如果你有至少2个委托,它将导致你遇到的问题。
效果就像这个截图: 重现步骤: 1为tableview添加一个新项; 2删除刚刚添加的项目; 3再次添加新项目; 4删除刚刚添加的项目;
因为有2个委托,所以它抛出异常。
最后,我已将完整的示例代码上传到我的GitHub,如果需要,请下载。