UICollectionView Performing Updates using performBatchUpdates

I have a UICollectionView which I am trying to insert items into it dynamically/with animation. So I have some function that downloads images asynchronously and would like to insert the items in batches.

Once I have my data, I would like to do the following:

  • Swift - tableView Row height updates only after scrolling or toggle expand/collapse
  • How to load a KML file URL into Google Maps using iOS API?
  • How to solve timeout issues caused by bad HTTP persistent connection?
  • UITableCell AccessoryView: Setting accessoryView equal to UIImageView infinite loop
  • How to make appium tests run faster on iOS?
  • WhatsApp image sharing iOS
  • [self.collectionView performBatchUpdates:^{
        for (UIImage *image in images) {
            [self.collectionView insertItemsAtIndexPaths:****]
        }
    } completion:nil];
    

    Now in place of the ***, I should be passing an array of NSIndexPaths, which should point to the location of the new items to be inserted. I am very confused since after providing the location, how do I provide the actual image that should be displayed at that position?

    Thank you


    UPDATE:

    resultsSize contains the size of the data source array, self.results, before new data is added from the data at newImages.

    [self.collectionView performBatchUpdates:^{
    
        int resultsSize = [self.results count];
        [self.results addObjectsFromArray:newImages];
        NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
    
        for (int i = resultsSize; i < resultsSize + newImages.count; i++)
              [arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
    
              [self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
    
    } completion:nil];
    

    3 Solutions Collect From Internet About “UICollectionView Performing Updates using performBatchUpdates”

    See Inserting, Deleting, and Moving Sections and Items from the “Collection View Programming Guide for iOS”:

    To insert, delete, or move a single section or item, you must follow
    these steps:

    1. Update the data in your data source object.
    2. Call the appropriate method of the collection view to insert or delete the section or item.

    It is critical that you update your data source before notifying the
    collection view of any changes. The collection view methods assume
    that your data source contains the currently correct data. If it does
    not, the collection view might receive the wrong set of items from
    your data source or ask for items that are not there and crash your
    app.

    So in your case, you must add an image to the collection view data source first and then call insertItemsAtIndexPaths. The collection view will then ask the data source delegate function to provide the view for the inserted item.

    I just implemented that with Swift. So I would like to share my implementation.
    First initialise an array of NSBlockOperations:

        var blockOperations: [NSBlockOperation] = []
    

    In controller will change, re-init the array:

    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        blockOperations.removeAll(keepCapacity: false)
    }
    

    In the did change object method:

        func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    
        if type == NSFetchedResultsChangeType.Insert {
            println("Insert Object: \(newIndexPath)")
    
            blockOperations.append(
                NSBlockOperation(block: { [weak self] in
                    if let this = self {
                        this.collectionView!.insertItemsAtIndexPaths([newIndexPath!])
                    }
                })
            )
        }
        else if type == NSFetchedResultsChangeType.Update {
            println("Update Object: \(indexPath)")
            blockOperations.append(
                NSBlockOperation(block: { [weak self] in
                    if let this = self {
                        this.collectionView!.reloadItemsAtIndexPaths([indexPath!])
                    }
                })
            )
        }
        else if type == NSFetchedResultsChangeType.Move {
            println("Move Object: \(indexPath)")
    
            blockOperations.append(
                NSBlockOperation(block: { [weak self] in
                    if let this = self {
                        this.collectionView!.moveItemAtIndexPath(indexPath!, toIndexPath: newIndexPath!)
                    }
                })
            )
        }
        else if type == NSFetchedResultsChangeType.Delete {
            println("Delete Object: \(indexPath)")
    
            blockOperations.append(
                NSBlockOperation(block: { [weak self] in
                    if let this = self {
                        this.collectionView!.deleteItemsAtIndexPaths([indexPath!])
                    }
                })
            )
        }
    }
    

    In the did change section method:

    func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    
        if type == NSFetchedResultsChangeType.Insert {
            println("Insert Section: \(sectionIndex)")
    
            blockOperations.append(
                NSBlockOperation(block: { [weak self] in
                    if let this = self {
                        this.collectionView!.insertSections(NSIndexSet(index: sectionIndex))
                    }
                })
            )
        }
        else if type == NSFetchedResultsChangeType.Update {
            println("Update Section: \(sectionIndex)")
            blockOperations.append(
                NSBlockOperation(block: { [weak self] in
                    if let this = self {
                        this.collectionView!.reloadSections(NSIndexSet(index: sectionIndex))
                    }
                })
            )
        }
        else if type == NSFetchedResultsChangeType.Delete {
            println("Delete Section: \(sectionIndex)")
    
            blockOperations.append(
                NSBlockOperation(block: { [weak self] in
                    if let this = self {
                        this.collectionView!.deleteSections(NSIndexSet(index: sectionIndex))
                    }
                })
            )
        }
    }
    

    And finally, in the did controller did change content method:

    func controllerDidChangeContent(controller: NSFetchedResultsController) {        
        collectionView!.performBatchUpdates({ () -> Void in
            for operation: NSBlockOperation in self.blockOperations {
                operation.start()
            }
        }, completion: { (finished) -> Void in
            self.blockOperations.removeAll(keepCapacity: false)
        })
    }
    

    I personally added some code in the deinit method as well, in order to cancel the operations when the ViewController is about to get deallocated:

    deinit {
        // Cancel all block operations when VC deallocates
        for operation: NSBlockOperation in blockOperations {
            operation.cancel()
        }
    
        blockOperations.removeAll(keepCapacity: false)
    }
    

    I was facing the similar issue while deleting the item from index and this is what i think we need to do while using performBatchUpdates: method.

    1# first call deleteItemAtIndexPath to delete the item from collection view.

    2# Delete the element from array.

    3# Update collection view by reloading data.

    [self.collectionView performBatchUpdates:^{
                NSIndexPath *indexPath = [NSIndexPath indexPathForRow:sender.tag inSection:0];
                [self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
                [self.addNewDocumentArray removeObjectAtIndex:sender.tag];
            } completion:^(BOOL finished) {
                [self.collectionView reloadData];
            }];
    

    This help me to remove all the crash and assertion failures.