使用Xamarin.iOS在UICollectionViewCell中添加带有单击处理程序的按钮

使用Xamarin.iOS在UICollectionViewCell中添加带有单击处理程序的按钮,xamarin.ios,uicollectionview,Xamarin.ios,Uicollectionview,我正在将以前使用UITableView的屏幕重写为UICollectionView。但是我在单元格内的按钮上遇到了点击处理程序的问题 collectionView.RegisterNibForCell (DocumentViewCell.Nib, docCellId); ... public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSInd

我正在将以前使用UITableView的屏幕重写为UICollectionView。但是我在单元格内的按钮上遇到了点击处理程序的问题

collectionView.RegisterNibForCell (DocumentViewCell.Nib, docCellId);    
...

public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
    var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (docCellId, indexPath);                
    docCell.BtnDelete.Hidden = !EditMode;
    docCell.BtnDelete.TouchUpInside += delegate(object sender, EventArgs e) {
        Logging.Debug("Crashes before if reaches here");
    };
    return docCell;
}
我知道单元格被重新使用,这种情况下很可能无法正常工作,但现在当按下delete按钮时,它会立即崩溃,列表中只有一个元素。一切都很好,直到我按下按钮,然后我得到下面的堆栈跟踪。基于我的UITableView代码几乎相同,我看不出发生这种情况的原因

是否有人使用基于Nib的CollectionView单元格来完成此操作?非常感谢您的帮助

堆栈跟踪:

 at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication.UIApplicationMain (int,string[],intptr,intptr) <IL 0x0009f, 0xffffffff>
 at MonoTouch.UIKit.UIApplication.Main (string[],string,string) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
 at SalesApp.Application.Main (string[]) [0x00000] in /MyApp/Main.cs:18
 at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>

Native stacktrace:

0   SalesApp                            0x0009a85c mono_handle_native_sigsegv + 284
1   SalesApp                            0x0000e138 mono_sigsegv_signal_handler + 248
2   libsystem_c.dylib                   0x990a78cb _sigtramp + 43
3   ???                                 0xffffffff 0x0 + 4294967295
4   UIKit                               0x01990258 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61
5   UIKit                               0x01a51021 -[UIControl sendAction:to:forEvent:] + 66
6   UIKit                               0x01a5157f -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 578
7   UIKit                               0x01a506e8 -[UIControl touchesEnded:withEvent:] + 546
8   UIKit                               0x01c541d3 _UIGestureRecognizerUpdate + 7407
9   CoreFoundation                      0x03ecbafe __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30

我猜
DequeueReusableCell
返回
null
,因为它是第一个被绘制的单元格

您的代码应该如下所示:

public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
    var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (docCellId, indexPath);
    if (docCell == null)
        docCell = new UICollectionViewCell (...);
    docCell.BtnDelete.TouchUpInside += delegate(object sender, EventArgs e) {
        Logging.Debug("Crashes before if reaches here");
    };
    return docCell;
}

将省略号
(…)
替换为创建单元格的任何内容。

问题是没有对从
GetCell
方法返回的
docCell
实例的托管引用。这意味着GC可以随时收集它

当再次需要它时,它将重新出现在托管世界中(使用
IntPtr
构造函数)。它将是相同的(重复使用)本地实例,但是一个新的托管实例。发生崩溃是因为事件指向旧的托管实例(已收集)

简单的解决方案是在视图存在时保留已创建单元的缓存。这将确保GC在使用(托管)单元格之前不会收集这些单元格。对于
UITableView
,有几个答案显示了这一点


注意:我想我们在最新版本的Xamarin.iOS中修复(隐藏)了这个问题。也许这只是为了
UITableView
!?!需要检查这个…

对不起,没有:-),然后我会得到一个空指针。此方法添加在iOS6中,并始终返回有效的单元格。查看此链接以获取与我的代码相同的示例:
UICollectionView
,与
UITableView
的新(iOS6)API类似,允许您注册要创建的单元格的
类。因此,不再需要检查null(更少的代码:-),我应该说新的API要求(不允许)您注册。
UITableView
的旧
DequeueReusableCell
仍然可以与旧模式一起使用(如果返回null则创建),但新重载(新选择器)还需要注册。垃圾收集单元的eventhandler似乎仍处于活动状态,因为如果我将按钮事件连接重写为docCell.BtnDelete.TouchUpInside-=HandleTouchUpInside;和docCell.BtnDelete.toucupinside+=HandleTouchUpInside,则一切正常。我编辑了我的问题,以显示我隐藏和取消隐藏这个按钮,所以它可能与此相关?不管怎样,你把我放在正确的轨道上,所以我会把它标记为正确的。嗯。。。也许这场车祸马上把我甩了。。。早期代码的一个不同问题是,在重新使用单元格时,可能会多次重新分配事件(在第二种情况下不会发生这种情况)。你能用一个独立的测试用例提交一个bug报告吗?我们会看一看(至少要确定触发崩溃的条件)。是的,已经用它做了一个测试用例,所以我明天会提交给bug追踪器,你可以看一看。
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
    var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (docCellId, indexPath);
    if (docCell == null)
        docCell = new UICollectionViewCell (...);
    docCell.BtnDelete.TouchUpInside += delegate(object sender, EventArgs e) {
        Logging.Debug("Crashes before if reaches here");
    };
    return docCell;
}