iOS 11中TableView单元格按钮的订阅/取消订阅点击事件失效
这个问题我之前做Xamarin.iOS项目时也碰到过,核心就是TableView的单元格复用机制在搞鬼!咱们一步步理清楚问题出在哪,再给你几个靠谱的解决办法。
为什么会出现点击多次触发的情况?
- 当你刷新TableView数据时,系统会复用已经创建过的单元格,而不是每次都新建。如果你的事件绑定逻辑只在
GetCell里做「先减再加」,但某次复用的时候,按钮上可能已经绑定了不止一次事件(比如之前的移除操作没生效,或者旧ViewController实例的事件还被按钮持有),每次复用又加一次,自然点一下触发多次。 - 另外,当你关闭再重新打开ViewController时,旧的ViewController实例可能还没被GC回收,而之前的单元格还持有它的事件引用,新的ViewController又绑定了一次,点击就会同时触发新旧实例的事件。
解决方案1:在单元格的PrepareForReuse里统一清理(最推荐)
这是苹果官方推荐的处理单元格复用的方式,PrepareForReuse会在单元格被复用时自动调用,咱们在这里把事件、Tag这些状态都重置掉:
首先自定义你的单元格:
public class CustomCell : UITableViewCell { public UIButton btn_Click { get; private set; } // 用自定义事件把点击事件传递给ViewController,避免直接绑定VC的方法 public event EventHandler<int> OnButtonClicked; public CustomCell(NSString reuseId) : base(UITableViewCellStyle.Default, reuseId) { // 初始化按钮 btn_Click = new UIButton(UIButtonType.System) { Frame = new CGRect(20, 10, 120, 35) }; // 只在单元格初始化时绑定一次内部的点击处理方法 btn_Click.TouchUpInside += HandleButtonTap; ContentView.AddSubview(btn_Click); } private void HandleButtonTap(object sender, EventArgs e) { // 把点击事件传递出去,带上当前的行号Tag OnButtonClicked?.Invoke(this, btn_Click.Tag); } public override void PrepareForReuse() { base.PrepareForReuse(); // 重置Tag,避免复用后带旧的行号 btn_Click.Tag = -1; // 移除ViewController之前绑定的事件,避免重复绑定 OnButtonClicked = null; } }
然后在你的ViewController的GetCell方法里:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell("CustomCell") as CustomCell; if (cell == null) { cell = new CustomCell(new NSString("CustomCell")); } // 绑定自定义事件,处理点击逻辑 cell.OnButtonClicked += (s, row) => { // 这里写你的点击处理代码,row就是对应的行号 Console.WriteLine($"点击了第{row}行的按钮"); }; // 设置当前行号为Tag cell.btn_Click.Tag = indexPath.Row; // 其他单元格内容设置... return cell; }
解决方案2:用Command模式(适合MVVM架构)
如果你的项目用了MVVM,这种方式更优雅,不需要手动处理事件订阅:
修改自定义单元格:
public class CustomCell : UITableViewCell { public UIButton btn_Click { get; private set; } public ICommand ButtonCommand { get; set; } public object CommandParam { get; set; } public CustomCell(NSString reuseId) : base(UITableViewCellStyle.Default, reuseId) { btn_Click = new UIButton(UIButtonType.System) { Frame = new CGRect(20, 10, 120, 35) }; btn_Click.TouchUpInside += (s, e) => ButtonCommand?.Execute(CommandParam); ContentView.AddSubview(btn_Click); } public override void PrepareForReuse() { base.PrepareForReuse(); // 重置Command和参数 ButtonCommand = null; CommandParam = null; } }
然后在ViewController的GetCell里:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell("CustomCell") as CustomCell; if (cell == null) { cell = new CustomCell(new NSString("CustomCell")); } // 绑定Command和行号参数 cell.ButtonCommand = new Command(() => HandleButtonTap(indexPath.Row)); cell.CommandParam = indexPath.Row; return cell; } private void HandleButtonTap(int row) { // 处理点击逻辑 }
解决方案3:修复你现有代码的问题(最小改动)
如果你不想改单元格结构,那就在ViewController销毁时主动清理所有单元格的事件绑定,同时保持GetCell里的逻辑:
首先在ViewController里添加清理逻辑:
public override void ViewWillDisappear(bool animated) { base.ViewWillDisappear(animated); // 遍历所有可见单元格,移除事件绑定 foreach (var cell in tableView.VisibleCells) { var customCell = cell as CustomCell; customCell?.btn_Click.TouchUpInside -= BtnClickEvent; } } protected override void Dispose(bool disposing) { if (disposing) { // 彻底清理所有单元格的事件绑定 foreach (var cell in tableView.VisibleCells) { var customCell = cell as CustomCell; customCell?.btn_Click.TouchUpInside -= BtnClickEvent; } tableView.Dispose(); } base.Dispose(disposing); }
然后你的GetCell方法保持不变,但要确保单元格是自定义的,按钮是成员变量:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell("CustomCell") as CustomCell; if (cell == null) { cell = new CustomCell(new NSString("CustomCell")); } // 先移除再添加,确保只有一次绑定 cell.btn_Click.TouchUpInside -= BtnClickEvent; cell.btn_Click.Tag = indexPath.Row; cell.btn_Click.TouchUpInside += BtnClickEvent; return cell; } private void BtnClickEvent(object sender, EventArgs e) { if (sender is UIButton btn) { int row = btn.Tag; // 处理点击逻辑 } }
内容的提问来源于stack exchange,提问作者Harshad Pansuriya
相关产品推荐
相关产品推荐

