C# UICollectionView-一个视图上的更新动画太多

C# UICollectionView-一个视图上的更新动画太多,c#,xamarin.ios,ios7,reactiveui,C#,Xamarin.ios,Ios7,Reactiveui,更新:已解决!有关解决方案,请参见下面的答案 我的应用程序在UICollectionView中显示许多图像。当新项目插入得太快,集合视图无法处理时,我当前遇到了InsertItemSatinDexpath问题。以下是例外情况: NSInternalInconsistencyException原因:更新动画太多 在一个视图中,每次飞行限制为31 结果表明,这是由于我的模型缓冲了多达20个新图像,并将它们一次推送到数据源,而不是在集合视图批更新块中。缺少批处理更新并不是因为我的懒惰,而是因为我的数据

更新:已解决!有关解决方案,请参见下面的答案

我的应用程序在UICollectionView中显示许多图像。当新项目插入得太快,集合视图无法处理时,我当前遇到了InsertItemSatinDexpath问题。以下是例外情况:

NSInternalInconsistencyException原因:更新动画太多 在一个视图中,每次飞行限制为31

结果表明,这是由于我的模型缓冲了多达20个新图像,并将它们一次推送到数据源,而不是在集合视图批更新块中。缺少批处理更新并不是因为我的懒惰,而是因为我的数据源之间有一个抽象层,它实际上是一个.Net可观察的集合(下面的代码)

我想知道的是,开发者应该如何防止在飞行中达到31个动画的硬编码限制?我的意思是当它发生的时候,你就完蛋了。那么苹果是怎么想的呢

阅读代码的Monotouch开发人员注意:

崩溃实际上是由UICollectionViewDataSourceFlatReadOnly使用CollectionChanged事件压倒UIDataBoundCollectionView造成的,它代表底层可观察集合代理控件。这会导致collectionview受到非批处理InsertItems调用的影响。(是的,保罗,这是一个反应性的集合)

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;
    }
  }
}
//
///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
}