更新:解决了!请参阅下面的答案以获取解决方案。
我的应用程序在 UICollectionView 中显示了许多图像。当新项目的插入速度太快以至于集合视图无法处理时,我目前遇到了 insertItemsAtIndexPaths 的问题。以下是例外情况:
NSInternalInconsistencyException 原因:一个视图上有太多更新动画 - 一次限制为 31 个在飞行中
事实证明,这是由于我的模型缓冲了多达 20 个新图像并将它们一次推送到数据源而不是在集合视图批量更新块内。没有批量更新不是由于我的懒惰造成的,而是因为我的数据源之间的抽象层实际上是一个 .Net Observable 集合(下面的代码)。
我想知道的是开发人员应该如何防止在飞行中达到 31 个动画的硬编码限制?我的意思是,当它发生时,你就完蛋了。那么苹果是怎么想的呢?
阅读代码的 Monotouch 开发人员注意:
崩溃实际上是由 UICollectionViewDataSourceFlatReadOnly 用 CollectionChanged 事件压倒 UIDataBoundCollectionView 引起的,它代表底层可观察集合代理到控件。这导致collectionview 被非批处理的InsertItems调用锤击。(是的,保罗,它是一个 ReactiveCollection)。
UIDataBoundCollectionView
/// <summary>
/// UITableView subclass that supports automatic updating in response
/// to DataSource changes if the DataSource supports INotifiyCollectionChanged
/// </summary>
[Register("UIDataBoundCollectionView")]
public class UIDataBoundCollectionView : UICollectionView,
IEnableLogger
{
public override NSObject WeakDataSource
{
get
{
return base.WeakDataSource;
}
set
{
var ncc = base.WeakDataSource as INotifyCollectionChanged;
if(ncc != null)
{
ncc.CollectionChanged -= OnDataSourceCollectionChanged;
}
base.WeakDataSource = value;
ncc = base.WeakDataSource as INotifyCollectionChanged;
if(ncc != null)
{
ncc.CollectionChanged += OnDataSourceCollectionChanged;
}
}
}
void OnDataSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NSIndexPath[] indexPaths;
switch(e.Action)
{
case NotifyCollectionChangedAction.Add:
indexPaths = IndexPathHelper.FromRange(e.NewStartingIndex, e.NewItems.Count);
InsertItems(indexPaths);
break;
case NotifyCollectionChangedAction.Remove:
indexPaths = IndexPathHelper.FromRange(e.OldStartingIndex, e.OldItems.Count);
DeleteItems(indexPaths);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
PerformBatchUpdates(() =>
{
for(int i=0; i<e.OldItems.Count; i++)
MoveItem(NSIndexPath.FromItemSection(e.OldStartingIndex + i, 0), NSIndexPath.FromItemSection(e.NewStartingIndex + i, 0));
}, null);
break;
case NotifyCollectionChangedAction.Reset:
ReloadData();
break;
}
}
}
UICollectionViewDataSourceFlatReadOnly
/// <summary>
/// Binds a table to an flat (non-grouped) items collection
/// Supports dynamically changing collections through INotifyCollectionChanged
/// </summary>
public class UICollectionViewDataSourceFlatReadOnly : UICollectionViewDataSource,
ICollectionViewDataSource,
INotifyCollectionChanged
{
/// <summary>
/// Initializes a new instance of the <see cref="UICollectionViewDataSourceFlat"/> class.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="items">The items.</param>
/// <param name="cellProvider">The cell provider</param>
public UICollectionViewDataSourceFlatReadOnly(IReadOnlyList<object> items, ICollectionViewCellProvider cellProvider)
{
this.items = items;
this.cellProvider = cellProvider;
// wire up proxying collection changes if supported by source
var ncc = items as INotifyCollectionChanged;
if(ncc != null)
{
// wire event handler
ncc.CollectionChanged += OnItemsChanged;
}
}
#region Properties
private IReadOnlyList<object> items;
private readonly ICollectionViewCellProvider cellProvider;
#endregion
#region Overrides of UICollectionViewDataSource
public override int NumberOfSections(UICollectionView collectionView)
{
return 1;
}
public override int GetItemsCount(UICollectionView collectionView, int section)
{
return items.Count;
}
/// <summary>
/// Gets the cell.
/// </summary>
/// <param name="tableView">The table view.</param>
/// <param name="indexPath">The index path.</param>
/// <returns></returns>
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
// reuse or create new cell
var cell = (UICollectionViewCell) collectionView.DequeueReusableCell(cellProvider.Identifier, indexPath);
// get the associated collection item
var item = GetItemAt(indexPath);
// update the cell
if(item != null)
cellProvider.UpdateCell(cell, item, collectionView.GetIndexPathsForSelectedItems().Contains(indexPath));
// done
return cell;
}
#endregion
#region Implementation of ICollectionViewDataSource
/// <summary>
/// Gets the item at.
/// </summary>
/// <param name="indexPath">The index path.</param>
/// <returns></returns>
public object GetItemAt(NSIndexPath indexPath)
{
return items[indexPath.Item];
}
public int ItemCount
{
get
{
return items.Count;
}
}
#endregion
#region INotifyCollectionChanged implementation
// UIDataBoundCollectionView will subscribe to this event
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(CollectionChanged != null)
CollectionChanged(sender, e);
}
}