【问题标题】:UICollectionView Performing Updates using performBatchUpdatesUICollectionView 使用 performBatchUpdates 执行更新
【发布时间】:2012-09-21 07:41:53
【问题描述】:

我有一个UICollectionView,我正在尝试将项目动态/带有动画插入其中。所以我有一些功能可以异步下载图像并希望批量插入项目。

获得数据后,我想执行以下操作:

[self.collectionView performBatchUpdates:^{
    for (UIImage *image in images) {
        [self.collectionView insertItemsAtIndexPaths:****]
    }
} completion:nil];

现在代替***,我应该传递一个NSIndexPaths 数组,它应该指向要插入的新项目的位置。我很困惑,因为在提供了位置之后,我该如何提供应该在该位置显示的实际图像?

谢谢


更新:

resultsSize 包含从newImages 的数据添加新数据之前数据源数组self.results 的大小。

[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];

【问题讨论】:

    标签: objective-c ios xcode ipad uicollectionview


    【解决方案1】:

    参见“Collection View Programming Guide for iOS”中的Inserting, Deleting, and Moving Sections and Items

    要插入、删除或移动单个部分或项目,您必须遵循 这些步骤:

    1. 更新数据源对象中的数据。
    2. 调用集合视图的适当方法来插入或删除部分或项目。

    在通知 任何更改的集合视图。集合视图方法假设 您的数据源包含当前正确的数据。如果确实如此 不是,集合视图可能会收到错误的项目集 您的数据源或询问不存在的项目并使您的 应用程序。

    因此,在您的情况下,您必须先将图像添加到集合视图数据源,然后调用insertItemsAtIndexPaths。然后,集合视图将要求数据源委托函数为插入的项目提供视图。

    【讨论】:

    • 所以对于插入,我需要两个数组:dataSource 的数组,然后是一个包含新项目的数组。我应该将新项目添加到数据源数组中,然后使用数据源数组中第一个新项目的索引路径调用更新。对吗?
    • @Darksky:差不多。您应该首先将新项目添加到数据源数组中,然后为数据源数组中的所有新项目调用insertItemsAtIndexPaths:。您还可以向数据源数组添加一个新项目,为该项目调用insertItemsAtIndexPaths:,然后对所有要添加的项目重复此过程。由于您正在进行批量更新,因此不会有任何区别。唯一重要的是:当您调用insertItemsAtIndexPaths: 时,数据源必须已经相应更新。
    • 我对您所描述的内容有疑问,发布于:stackoverflow.com/questions/12665669/…
    • 我实际上遇到了问题。最初,当 performUpdates 将图像插入到一个空的集合视图中时,它显示得很好。之后,后续更新不是插入新数据,而是从位置 1-5 插入图像,然后是 2-6、3-7(我一次插入 4 个图像)等......而不是插入 1-4, 5-8。所以它只是移动一个位置并重新插入那里的所有内容,而不是跳过旧图像并只插入新图像。我的包含索引路径的数组是正确的。请参阅上面包含我的代码的编辑。
    • UICollectionViews 简直太荒谬了。我所拥有的只是一个带有数据源数组的集合视图,其中包含我使用 GCD 在线下载并在主线程中更新 UI 的数据。而已!集合视图甚至无法更改其内容大小以允许自动滚动到项目。你能把你的全班贴在某个地方让我看看吗?我已经与集合视图斗争了 3 天,而 Apple 声称它们是新的表视图。我真的很紧张,我的截止日期非常接近。
    【解决方案2】:

    我刚刚用 Swift 实现了它。所以我想分享我的实现。 首先初始化一个NSBlockOperations数组:

        var blockOperations: [NSBlockOperation] = []
    

    在控制器会改变,重新初始化数组:

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

    在确实改变对象方法中:

        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!])
                    }
                })
            )
        }
    }
    

    在确实更改部分方法中:

    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))
                    }
                })
            )
        }
    }
    

    最后,在 did 控制器中确实改变了内容方法:

    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)
        })
    }
    

    我个人也在deinit方法中添加了一些代码,以便在ViewController即将被释放时取消操作:

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

    【讨论】:

    • 我把这个样板代码放在一个专门的类 CollectionViewLayoutUpdater 中以供重用。谢谢分享!
    【解决方案3】:

    我在从索引中删除项目时遇到了类似的问题,我认为这是我们在使用performBatchUpdates: 方法时需要做的。

    1#首先调用deleteItemAtIndexPath从collection view中删除item。

    2#从数组中删除元素。

    3# 通过重新加载数据来更新集合视图。

    [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];
            }];
    

    这有助于我消除所有崩溃和断言失败。

    【讨论】:

    • 插入或删除后不需要重新加载collectionview。但是您的解决方案运行良好。
    • 这不是办法。您需要做的就是 (1) 从数组中删除元素,然后 (2) 调用 -deleteItemsAtIndexPaths:。您不需要将其包装在-performBatchUpdates 中,也不需要调用-reloadData。
    • @LukasPetr 我认为问题是“如何在 collectionView 中使用 performBatchUpdates”。对于上述问题我处理的方式,它帮助我消除了崩溃以及断言失败。
    • @Nik,这是唯一的方法,看起来其他人不明白问题是什么,这是在使用块时正确更新索引的唯一方法在自定义集合单元格内,非常好
    • 此代码将消除崩溃,但之后您将重新加载整个表。您也可以直接调用 [self.collectionView reloadData];.... 这不会给您带来移除效果。
    猜你喜欢
    • 2017-02-03
    • 1970-01-01
    • 1970-01-01
    • 2012-09-15
    • 1970-01-01
    • 2016-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多