title: UICollectionView - too many update animations on one view
body: <p><strong>Update:</strong> Solved! See my answer below for the solution.</p>
  
  <p>My app displays a number of images in a UICollectionView. And I'm currently experiencing a problem with insertItemsAtIndexPaths when new items are getting inserted too fast for the collection view to handle. Below is the exception:</p>
  
  <blockquote>
    <p>NSInternalInconsistencyException Reason:  too many update animations
    on one view - limit is 31 in flight at a time</p>
  </blockquote>
  
  <p>Turns out this was caused by my model buffering up to 20 new images and pushing them to the datasource at once but not inside a collection view batch update block. The absence of the batch update is not caused by laziness on my part but because of an abstraction layer between my datasource which is actually a .Net Observable collection (code below).</p>
  
  <p>What I would like to know is how is the developer supposed to prevent hitting the hard coded limit of 31 animations in flight? I mean when it happens, you are toast. So what was Apple thinking?</p>
  
  <p><em>Note to Monotouch Developers reading the code:</em></p>
  
  <p>The crash is effectively caused by the UICollectionViewDataSourceFlatReadOnly overwhelming UIDataBoundCollectionView with CollectionChanged events which it proxies to the control on behalf of the underlying observable collection. Which results in the collectionview getting hammered with non-batched <em>InsertItems</em> calls. (yes Paul, its a ReactiveCollection).</p>
  
  <p><strong>UIDataBoundCollectionView</strong></p>
  
  <pre><code>/// &lt;summary&gt;
  /// UITableView subclass that supports automatic updating in response 
  /// to DataSource changes if the DataSource supports INotifiyCollectionChanged
  /// &lt;/summary&gt;
  [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(() =&gt;
          {
            for(int i=0; i&lt;e.OldItems.Count; i++)
              MoveItem(NSIndexPath.FromItemSection(e.OldStartingIndex + i, 0), NSIndexPath.FromItemSection(e.NewStartingIndex + i, 0));
          }, null);
          break;
  
        case NotifyCollectionChangedAction.Reset:
          ReloadData();
          break;
      }
    }
  }
  </code></pre>
  
  <p><strong>UICollectionViewDataSourceFlatReadOnly</strong></p>
  
  <pre><code>/// &lt;summary&gt;
  /// Binds a table to an flat (non-grouped) items collection 
  /// Supports dynamically changing collections through INotifyCollectionChanged 
  /// &lt;/summary&gt;
  public class UICollectionViewDataSourceFlatReadOnly : UICollectionViewDataSource,
    ICollectionViewDataSource,
    INotifyCollectionChanged
  {
    /// &lt;summary&gt;
    /// Initializes a new instance of the &lt;see cref="UICollectionViewDataSourceFlat"/&gt; class.
    /// &lt;/summary&gt;
    /// &lt;param name="table"&gt;The table.&lt;/param&gt;
    /// &lt;param name="items"&gt;The items.&lt;/param&gt;
    /// &lt;param name="cellProvider"&gt;The cell provider&lt;/param&gt;
    public UICollectionViewDataSourceFlatReadOnly(IReadOnlyList&lt;object&gt; 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&lt;object&gt; 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;
    }
  
    /// &lt;summary&gt;
    /// Gets the cell.
    /// &lt;/summary&gt;
    /// &lt;param name="tableView"&gt;The table view.&lt;/param&gt;
    /// &lt;param name="indexPath"&gt;The index path.&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    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
  
    /// &lt;summary&gt;
    /// Gets the item at.
    /// &lt;/summary&gt;
    /// &lt;param name="indexPath"&gt;The index path.&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    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);
    }
  }
  </code></pre>