C# UICollectionView-一个视图上的更新动画太多
更新:已解决!有关解决方案,请参见下面的答案 我的应用程序在UICollectionView中显示许多图像。当新项目插入得太快,集合视图无法处理时,我当前遇到了InsertItemSatinDexpath问题。以下是例外情况: NSInternalInconsistencyException原因:更新动画太多 在一个视图中,每次飞行限制为31 结果表明,这是由于我的模型缓冲了多达20个新图像,并将它们一次推送到数据源,而不是在集合视图批更新块中。缺少批处理更新并不是因为我的懒惰,而是因为我的数据源之间有一个抽象层,它实际上是一个.Net可观察的集合(下面的代码) 我想知道的是,开发者应该如何防止在飞行中达到31个动画的硬编码限制?我的意思是当它发生的时候,你就完蛋了。那么苹果是怎么想的呢 阅读代码的Monotouch开发人员注意: 崩溃实际上是由UICollectionViewDataSourceFlatReadOnly使用CollectionChanged事件压倒UIDataBoundCollectionView造成的,它代表底层可观察集合代理控件。这会导致collectionview受到非批处理InsertItems调用的影响。(是的,保罗,这是一个反应性的集合) UIDataBoundCollectionViewC# UICollectionView-一个视图上的更新动画太多,c#,xamarin.ios,ios7,reactiveui,C#,Xamarin.ios,Ios7,Reactiveui,更新:已解决!有关解决方案,请参见下面的答案 我的应用程序在UICollectionView中显示许多图像。当新项目插入得太快,集合视图无法处理时,我当前遇到了InsertItemSatinDexpath问题。以下是例外情况: NSInternalInconsistencyException原因:更新动画太多 在一个视图中,每次飞行限制为31 结果表明,这是由于我的模型缓冲了多达20个新图像,并将它们一次推送到数据源,而不是在集合视图批更新块中。缺少批处理更新并不是因为我的懒惰,而是因为我的数据
/// <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;
}
}
}
//
///UITableView子类,支持响应自动更新
///如果数据源支持InotifyCollectionChanged,则更改数据源
///
[注册(“UIDataBoundCollectionView”)]
公共类UIDataBoundCollectionView:UICollectionView,
IEnableLogger
{
公共覆盖NSObject WeakDataSource
{
得到
{
返回base.WeakDataSource;
}
设置
{
var ncc=base.WeakDataSource作为INotifyCollectionChanged;
如果(ncc!=null)
{
ncc.CollectionChanged-=OnDataSourceCollectionChanged;
}
base.WeakDataSource=值;
ncc=base.WeakDataSource作为INotifyCollectionChanged;
如果(ncc!=null)
{
ncc.CollectionChanged+=OnDataSourceCollectionChanged;
}
}
}
void OnDataSourceCollectionChanged(对象发送方,NotifyCollectionChangedEventArgs e)
{
nsindepath[]索引路径;
开关(电动)
{
案例NotifyCollectionChangedAction。添加:
indexPaths=IndexPathHelper.FromRange(e.NewStartingIndex,e.NewItems.Count);
插入项(indexPaths);
打破
案例NotifyCollectionChangedAction。删除:
indexPaths=IndexPathHelper.FromRange(e.OldStartingIndex,e.OldItems.Count);
删除项(索引路径);
打破
案例通知收集更改操作。替换:
案例通知收集更改操作。移动:
性能更新(()=>
{
对于(int i=0;i更新:,在我写下这个答案将近一年后,我强烈建议使用ReactiveUI CollectionView/TableView绑定功能。它现在处于更成熟的状态。
解决方案比预期的要困难一些。多亏了RX,在UICollectionViewDataSourceFlatReadOnly中控制每项插入或删除的速率很容易解决。下一步涉及在UIDataBoundCollectionView中批处理这些更改。PerformBatchUpdate在这里没有帮助,但发出了单个插入项使用所有插入的索引路径调用确实解决了问题
由于UICollectionView验证其内部一致性的方式(即,它在每次插入项或删除项后调用GetItemsCount等),我不得不将ItemCount管理移交给UIDataBoundCollectionView(这很难接受,但别无选择)
顺便说一句,性能是一流的
以下是所有感兴趣的人的更新来源:
ICollectionViewDataSource
public interface ICollectionViewDataSource
{
/// <summary>
/// Gets the bound item at the specified index
/// </summary>
/// <param name="indexPath">The index path.</param>
/// <returns></returns>
object GetItemAt(NSIndexPath indexPath);
/// <summary>
/// Gets the actual item count.
/// </summary>
/// <value>The item count.</value>
int ActualItemCount { get; }
/// <summary>
/// Gets or sets the item count reported to UIKit
/// </summary>
/// <value>The item count.</value>
int ItemCount { get; set; }
/// <summary>
/// Observable providing change monitoring
/// </summary>
/// <value>The collection changed observable.</value>
IObservable<NotifyCollectionChangedEventArgs[]> CollectionChangedObservable { get; }
}
公共接口ICollectionViewDataSource
{
///
///获取指定索引处的绑定项
///
///索引路径。
///
对象GetItemAt(NSIndexPath indexPath);
///
///获取实际项目计数。
///
///物品计数。
int实现项计数{get;}
///
///获取或设置报告给UIKit的项计数
///
///物品计数。
int ItemCount{get;set;}
///
///提供可观察的变更监控
///
///集合的变化是可见的。
IObservable CollectionChangedObservable{get;}
}
UIDataBoundCollectionView
[Register("UIDataBoundCollectionView")]
public class UIDataBoundCollectionView : UICollectionView,
IEnableLogger
{
public UIDataBoundCollectionView (NSObjectFlag t) : base(t)
{
}
public UIDataBoundCollectionView (IntPtr handle) : base(handle)
{
}
public UIDataBoundCollectionView (RectangleF frame, UICollectionViewLayout layout) : base(frame, layout)
{
}
public UIDataBoundCollectionView (NSCoder coder) : base(coder)
{
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if(collectionChangedSubscription != null)
{
collectionChangedSubscription.Dispose();
collectionChangedSubscription = null;
}
}
IDisposable collectionChangedSubscription;
public override NSObject WeakDataSource
{
get
{
return base.WeakDataSource;
}
set
{
if(collectionChangedSubscription != null)
{
collectionChangedSubscription.Dispose();
collectionChangedSubscription = null;
}
base.WeakDataSource = value;
collectionChangedSubscription = ICVS.CollectionChangedObservable
.Subscribe(OnDataSourceCollectionChanged);
}
}
ICollectionViewDataSource ICVS
{
get { return (ICollectionViewDataSource) WeakDataSource; }
}
void OnDataSourceCollectionChanged(NotifyCollectionChangedEventArgs[] changes)
{
List<NSIndexPath> indexPaths = new List<NSIndexPath>();
int index = 0;
for(;index<changes.Length;index++)
{
var e = changes[index];
switch(e.Action)
{
case NotifyCollectionChangedAction.Add:
indexPaths.AddRange(IndexPathHelper.FromRange(e.NewStartingIndex, e.NewItems.Count));
ICVS.ItemCount++;
// attempt to batch subsequent changes of the same type
if(index < changes.Length - 1)
{
for(int i=index + 1; i<changes.Length; i++)
{
if(changes[i].Action == e.Action)
{
indexPaths.AddRange(IndexPathHelper.FromRange(changes[i].NewStartingIndex, changes[i].NewItems.Count));
index++;
ICVS.ItemCount++;
}
}
}
InsertItems(indexPaths.ToArray());
indexPaths.Clear();
break;
case NotifyCollectionChangedAction.Remove:
indexPaths.AddRange(IndexPathHelper.FromRange(e.OldStartingIndex, e.OldItems.Count));
ICVS.ItemCount--;
// attempt to batch subsequent changes of the same type
if(index < changes.Length - 1)
{
for(int i=index + 1; i<changes.Length; i++)
{
if(changes[i].Action == e.Action)
{
indexPaths.AddRange(IndexPathHelper.FromRange(changes[i].OldStartingIndex, changes[i].OldItems.Count));
index++;
ICVS.ItemCount--;
}
}
}
DeleteItems(indexPaths.ToArray());
indexPaths.Clear();
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:
ICVS.ItemCount = ICVS.ActualItemCount;
ReloadData();
break;
}
}
}
}
[注册(“UIDataBoundCollectionView”)]
公共类UIDataBoundCollectionView:UICollectionView,
IEnableLogger
{
公共UIDataBoundCollectionView(NSObjectFlag t):基本(t)
{
}
公共UIDataBoundCollectionView(IntPtr句柄):基(句柄)
{
}
公共UIDataBoundCollectionView(矩形框架,UICollectionViewLayout布局):基础(框架,布局)
{
}
公共UIDataBoundCollectionView(NSCoder编码器):基本(编码器)
{
}
受保护的覆盖无效处置(布尔处置)
{
基地。处置(处置);
if(collectionChangedSubscription!=null)
{
collectionChangedSubscription.Dispose();
collectionChangedSubscription=null;
}
}
IDisposable collectionChangedSubscription;
公共覆盖NSObject WeakDataSource
{
得到
{
返回base.WeakDataSource;
}
设置
{
if(collectionChangedSubscription!=null)
{
collectionChangedSubscription.Dispose();
collectionChangedSubscription=null;
}
base.WeakDataSource=值;
collectionChangedSubscription=ICVS.CollectionChangedObservable
.订阅(OnDataSourceCollectionChanged);
}
}
ICollectionViewDataSource ICV
{
获取{return(ICollectionViewDataSource)WeakDataSource;}
}
void OnDataSourceCollectionChanged(NotifyCollectionChan
[Register("UIDataBoundCollectionView")]
public class UIDataBoundCollectionView : UICollectionView,
IEnableLogger
{
public UIDataBoundCollectionView (NSObjectFlag t) : base(t)
{
}
public UIDataBoundCollectionView (IntPtr handle) : base(handle)
{
}
public UIDataBoundCollectionView (RectangleF frame, UICollectionViewLayout layout) : base(frame, layout)
{
}
public UIDataBoundCollectionView (NSCoder coder) : base(coder)
{
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if(collectionChangedSubscription != null)
{
collectionChangedSubscription.Dispose();
collectionChangedSubscription = null;
}
}
IDisposable collectionChangedSubscription;
public override NSObject WeakDataSource
{
get
{
return base.WeakDataSource;
}
set
{
if(collectionChangedSubscription != null)
{
collectionChangedSubscription.Dispose();
collectionChangedSubscription = null;
}
base.WeakDataSource = value;
collectionChangedSubscription = ICVS.CollectionChangedObservable
.Subscribe(OnDataSourceCollectionChanged);
}
}
ICollectionViewDataSource ICVS
{
get { return (ICollectionViewDataSource) WeakDataSource; }
}
void OnDataSourceCollectionChanged(NotifyCollectionChangedEventArgs[] changes)
{
List<NSIndexPath> indexPaths = new List<NSIndexPath>();
int index = 0;
for(;index<changes.Length;index++)
{
var e = changes[index];
switch(e.Action)
{
case NotifyCollectionChangedAction.Add:
indexPaths.AddRange(IndexPathHelper.FromRange(e.NewStartingIndex, e.NewItems.Count));
ICVS.ItemCount++;
// attempt to batch subsequent changes of the same type
if(index < changes.Length - 1)
{
for(int i=index + 1; i<changes.Length; i++)
{
if(changes[i].Action == e.Action)
{
indexPaths.AddRange(IndexPathHelper.FromRange(changes[i].NewStartingIndex, changes[i].NewItems.Count));
index++;
ICVS.ItemCount++;
}
}
}
InsertItems(indexPaths.ToArray());
indexPaths.Clear();
break;
case NotifyCollectionChangedAction.Remove:
indexPaths.AddRange(IndexPathHelper.FromRange(e.OldStartingIndex, e.OldItems.Count));
ICVS.ItemCount--;
// attempt to batch subsequent changes of the same type
if(index < changes.Length - 1)
{
for(int i=index + 1; i<changes.Length; i++)
{
if(changes[i].Action == e.Action)
{
indexPaths.AddRange(IndexPathHelper.FromRange(changes[i].OldStartingIndex, changes[i].OldItems.Count));
index++;
ICVS.ItemCount--;
}
}
}
DeleteItems(indexPaths.ToArray());
indexPaths.Clear();
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:
ICVS.ItemCount = ICVS.ActualItemCount;
ReloadData();
break;
}
}
}
}
public class UICollectionViewDataSourceFlatReadOnly : UICollectionViewDataSource,
ICollectionViewDataSource
{
/// <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)
{
collectionChangedObservable = Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
h => ncc.CollectionChanged += h, h => ncc.CollectionChanged -= h)
.SubscribeOn(TaskPoolScheduler.Default)
.Select(x => x.EventArgs)
.Buffer(TimeSpan.FromMilliseconds(100), 20)
.Where(x => x.Count > 0)
.Select(x => x.ToArray())
.ObserveOn(RxApp.MainThreadScheduler)
.StartWith(new[] { reset}); // ensure initial update
}
else
collectionChangedObservable = Observable.Return(reset);
}
#region Properties
private IReadOnlyList<object> items;
private readonly ICollectionViewCellProvider cellProvider;
IObservable<NotifyCollectionChangedEventArgs[]> collectionChangedObservable;
static readonly NotifyCollectionChangedEventArgs[] reset = new[] { new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) };
#endregion
#region Overrides of UICollectionViewDataSource
public override int NumberOfSections(UICollectionView collectionView)
{
return 1;
}
public override int GetItemsCount(UICollectionView collectionView, int section)
{
return ItemCount;
}
/// <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 ActualItemCount
{
get
{
return items.Count;
}
}
public int ItemCount { get; set; }
public IObservable<NotifyCollectionChangedEventArgs[]> CollectionChangedObservable
{
get
{
return collectionChangedObservable;
}
}
#endregion
}