2016-09-16 151 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

相關問題