【问题标题】:CRASH attempt to delete and reload the same index pathCRASH 尝试删除并重新加载相同的索引路径
【发布时间】:2015-06-02 23:16:49
【问题描述】:

CollectionViewController.m 第 439 行 __50-[CollectionViewController photoLibraryDidChange:]_block_invoke

致命异常:NSInternalInconsistencyException 尝试删除并重新加载相同的索引路径({length = 2, path = 0 - 26007})

- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
    // Call might come on any background queue. Re-dispatch to the main queue to handle it.
    dispatch_async(dispatch_get_main_queue(), ^{

        // check if there are changes to the assets (insertions, deletions, updates)
        PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
        if (collectionChanges) {

            // get the new fetch result
            self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];

            UICollectionView *collectionView = self.collectionView;

            if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
                // we need to reload all if the incremental diffs are not available
                [collectionView reloadData];

            } else {
                // if we have incremental diffs, tell the collection view to animate insertions and deletions
                [collectionView performBatchUpdates:^{
                    NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
                    if ([removedIndexes count]) {
                        [collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
                    }
                    NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
                    if ([insertedIndexes count]) {
                        [collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
                    }
                    NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
                    if ([changedIndexes count]) {
                        [collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
                    }
                } completion:NULL];
            }

            [self resetCachedAssets];
        }
    });
}

来源:https://developer.apple.com/devcenter/download.action?path=/wwdc_2014/wwdc_2014_sample_code/exampleappusingphotosframework.zip

我无法复制该问题。可能是什么问题呢?非常感谢!

【问题讨论】:

  • 我以前见过,最近无法重现,但我现在一直看到的是断言失败 *** -[UICollectionView _endItemAnimations] 中的断言失败,/ SourceCache/UIKit/UIKit-3318.93/UICollectionView.m:3720 然后 *** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“尝试从第 0 节中删除第 9 项,它在更新前仅包含 9 个项目”。这很奇怪,因为我的代码与示例中的应用程序完全相同,只是应用程序更复杂,而且它基于 Swift。 :(
  • 另外,我用这种方法看到的另一个问题与最终项目数与先前计数加上总和不匹配的断言错误有关。我相信计算这些索引并将其传递给侦听器的方式可能存在问题,或者在获取结果更新后,我们可能必须对数组进行额外验证,以验证集合视图的当前状态拉。老实说,这是我目前正在开发的应用程序中最令人沮丧的部分之一。
  • 有人创造了雷达吗?我会做。我测试了更新到 iOS 10 和 Swift 3 的最新代码,但它仍然经常崩溃。
  • 我已经测试了类似的代码,这些代码在使用 Xcode 8 beta 2 的 iOS 10 中发生了同样的错误,并且它不再崩溃了。正如我所怀疑的,这是 UIKit 中的一个错误。

标签: ios objective-c ios8 collectionview photokit


【解决方案1】:

我今天能够重现这个。为此,您需要:

  1. 打开正在侦听更改的应用
  2. 打开照片应用,将一组照片从 iCloud 共享相册保存到您的照片库中
  3. 转到照片应用,删除其中一些照片
  4. 再次转到 iCloud 共享相册并再次保存您删除的一些照片。您会看到这种情况发生。

我发现一个更新的代码似乎可以更好地处理这里的更新行为: https://developer.apple.com/library/ios/documentation/Photos/Reference/PHPhotoLibraryChangeObserver_Protocol/

但它仍然不能处理这种情况,也不能处理要删除的索引更大(即由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'试图从第0节中删除第9项,它之前只包含9个项目更新')。我创建了这个代码的更新版本,它可以更好地处理这个问题,并且到目前为止我还没有崩溃过。

func photoLibraryDidChange(changeInfo: PHChange!) {

    // Photos may call this method on a background queue;
    // switch to the main queue to update the UI.
    dispatch_async(dispatch_get_main_queue()) {


        // Check for changes to the list of assets (insertions, deletions, moves, or updates).
        if let collectionChanges = changeInfo.changeDetailsForFetchResult(self.assetsFetchResult) {

            // Get the new fetch result for future change tracking.
            self.assetsFetchResult = collectionChanges.fetchResultAfterChanges

            if collectionChanges.hasIncrementalChanges {

                // Get the changes as lists of index paths for updating the UI.
                var removedPaths: [NSIndexPath]?
                var insertedPaths: [NSIndexPath]?
                var changedPaths: [NSIndexPath]?
                if let removed = collectionChanges.removedIndexes {
                    removedPaths = self.indexPathsFromIndexSetWithSection(removed,section: 0)
                }
                if let inserted = collectionChanges.insertedIndexes {
                    insertedPaths = self.indexPathsFromIndexSetWithSection(inserted,section: 0)
                }
                if let changed = collectionChanges.changedIndexes {
                    changedPaths = self.indexPathsFromIndexSetWithSection(changed,section: 0)
                }
                var shouldReload = false
                if changedPaths != nil && removedPaths != nil{
                    for changedPath in changedPaths!{
                        if contains(removedPaths!,changedPath){
                            shouldReload = true
                            break
                        }
                    }

                }

                if removedPaths?.last?.item >= self.assetsFetchResult.count{
                    shouldReload = true
                }

                if shouldReload{
                    self.collectionView.reloadData()
                }else{
                    // Tell the collection view to animate insertions/deletions/moves
                    // and to refresh any cells that have changed content.
                    self.collectionView.performBatchUpdates(
                        {
                            if let theRemovedPaths = removedPaths {
                                self.collectionView.deleteItemsAtIndexPaths(theRemovedPaths)
                            }
                            if let theInsertedPaths = insertedPaths {
                                self.collectionView.insertItemsAtIndexPaths(theInsertedPaths)
                            }
                            if let theChangedPaths = changedPaths{
                                self.collectionView.reloadItemsAtIndexPaths(theChangedPaths)
                            }
                            if (collectionChanges.hasMoves) {
                                collectionChanges.enumerateMovesWithBlock() { fromIndex, toIndex in
                                    let fromIndexPath = NSIndexPath(forItem: fromIndex, inSection: 0)
                                    let toIndexPath = NSIndexPath(forItem: toIndex, inSection: 0)
                                    self.collectionView.moveItemAtIndexPath(fromIndexPath, toIndexPath: toIndexPath)
                                }
                            }
                        }, completion: nil)

                }

            } else {
                // Detailed change information is not available;
                // repopulate the UI from the current fetch result.
                self.collectionView.reloadData()
            }
        }
    }
}

func indexPathsFromIndexSetWithSection(indexSet:NSIndexSet?,section:Int) -> [NSIndexPath]?{
    if indexSet == nil{
        return nil
    }
    var indexPaths:[NSIndexPath] = []

    indexSet?.enumerateIndexesUsingBlock { (index, Bool) -> Void in
        indexPaths.append(NSIndexPath(forItem: index, inSection: section))
    }
    return indexPaths

}

Swift 3 / iOS 10 版本:

func photoLibraryDidChange(_ changeInstance: PHChange) {
    guard let collectionView = self.collectionView else {
        return
    }

    // Photos may call this method on a background queue;
    // switch to the main queue to update the UI.
    DispatchQueue.main.async {
        guard let fetchResults = self.fetchResults else {
            collectionView.reloadData()
            return
        }

        // Check for changes to the list of assets (insertions, deletions, moves, or updates).
        if let collectionChanges = changeInstance.changeDetails(for: fetchResults) {
            // Get the new fetch result for future change tracking.
            self.fetchResults = collectionChanges.fetchResultAfterChanges

            if collectionChanges.hasIncrementalChanges {
                // Get the changes as lists of index paths for updating the UI.
                var removedPaths: [IndexPath]?
                var insertedPaths: [IndexPath]?
                var changedPaths: [IndexPath]?
                if let removed = collectionChanges.removedIndexes {
                    removedPaths = self.indexPaths(from: removed, section: 0)
                }
                if let inserted = collectionChanges.insertedIndexes {
                    insertedPaths = self.indexPaths(from:inserted, section: 0)
                }
                if let changed = collectionChanges.changedIndexes {
                    changedPaths = self.indexPaths(from: changed, section: 0)
                }
                var shouldReload = false
                if let removedPaths = removedPaths, let changedPaths = changedPaths {
                    for changedPath in changedPaths {
                        if removedPaths.contains(changedPath) {
                            shouldReload = true
                            break
                        }
                    }
                }

                if let item = removedPaths?.last?.item {
                    if item >= fetchResults.count {
                        shouldReload = true
                    }
                }

                if shouldReload {
                    collectionView.reloadData()
                } else {
                    // Tell the collection view to animate insertions/deletions/moves
                    // and to refresh any cells that have changed content.
                    collectionView.performBatchUpdates({
                        if let theRemovedPaths = removedPaths {
                            collectionView.deleteItems(at: theRemovedPaths)
                        }
                        if let theInsertedPaths = insertedPaths {
                            collectionView.insertItems(at: theInsertedPaths)
                        }
                        if let theChangedPaths = changedPaths {
                            collectionView.reloadItems(at: theChangedPaths)
                        }

                        collectionChanges.enumerateMoves { fromIndex, toIndex in
                            collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
                                                    to: IndexPath(item: toIndex, section: 0))
                        }
                    })
                }
            } else {
                // Detailed change information is not available;
                // repopulate the UI from the current fetch result.
                collectionView.reloadData()
            }
        }
    }
}

func indexPaths(from indexSet: IndexSet?, section: Int) -> [IndexPath]? {
    guard let set = indexSet else {
        return nil
    }

    return set.map { (index) -> IndexPath in
        return IndexPath(item: index, section: section)
    }
}

【讨论】:

  • 感谢@batkru。这是对 Apple 的 bug infested 示例版本的改进。事实上,现在我想起来了,bug infested 可能是一个太好的说法,它更像是他们提出的示例代码的老鼠出没的沉船类型的木制品中的虫子。
  • iCloud相册更新频繁还是崩溃,只好改用简单的reloadData
  • @BlessingLopes 这可能不是示例代码中的错误,而是 UIKit 中的错误。我在 Xcode 8 beta 2 下使用 iOS 10 Simulator 测试了 Apple 示例代码的更新版本,它不再崩溃了。
  • 我为 Swift 3 / iOS 10 添加了一个工作代码版本
  • 感谢您的代码。但是我在单元格上的选择已经消失了。
【解决方案2】:

我在 Objective-C 中实现了 batkryu's answer 中的代码。

- (void)photoLibraryDidChange:(PHChange *)changeInstance {

    dispatch_async(dispatch_get_main_queue(), ^{

        PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
        if (collectionChanges) {

            self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];

            UICollectionView *collectionView = self.collectionView;
            NSArray *removedPaths;
            NSArray *insertedPaths;
            NSArray *changedPaths;

            if ([collectionChanges hasIncrementalChanges]) {
                NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
                removedPaths = [self indexPathsFromIndexSet:removedIndexes withSection:0];

                NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
                insertedPaths = [self indexPathsFromIndexSet:insertedIndexes withSection:0];

                NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
                changedPaths = [self indexPathsFromIndexSet:changedIndexes withSection:0];

                BOOL shouldReload = NO;

                if (changedPaths != nil && removedPaths != nil) {
                    for (NSIndexPath *changedPath in changedPaths) {
                        if ([removedPaths containsObject:changedPath]) {
                            shouldReload = YES;
                            break;
                        }
                    }
                }

                if (removedPaths.lastObject && ((NSIndexPath *)removedPaths.lastObject).item >= self.assetsFetchResults.count) {
                    shouldReload = YES;
                }

                if (shouldReload) {
                    [collectionView reloadData];

                } else {
                    [collectionView performBatchUpdates:^{
                        if (removedPaths) {
                            [collectionView deleteItemsAtIndexPaths:removedPaths];
                        }

                        if (insertedPaths) {
                            [collectionView insertItemsAtIndexPaths:insertedPaths];
                        }

                        if (changedPaths) {
                            [collectionView reloadItemsAtIndexPaths:changedPaths];
                        }

                        if ([collectionChanges hasMoves]) {
                            [collectionChanges enumerateMovesWithBlock:^(NSUInteger fromIndex, NSUInteger toIndex) {
                                NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex inSection:0];
                                NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex inSection:0];
                                [collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
                            }];
                        }

                    } completion:NULL];
                }

                [self resetCachedAssets];
            } else {
                [collectionView reloadData];
            }
        }
    });
}

- (NSArray *)indexPathsFromIndexSet:(NSIndexSet *)indexSet withSection:(int)section {
    if (indexSet == nil) {
        return nil;
    }
    NSMutableArray *indexPaths = [[NSMutableArray alloc] init];

    [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
        [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
    }];

    return indexPaths;
}

【讨论】:

  • 我在下面对您的答案进行了调整,为 performBatchUpdates 和分配给 assetsFetchResults 添加了一些同步。
【解决方案3】:

这是对@batkru 答案的改进,它消除了对变量shouldReload 的需要:

func photoLibraryDidChange(changeInstance: PHChange) {
    dispatch_async(dispatch_get_main_queue(), {
        let changeDetails = changeInstance.changeDetailsForFetchResult(self.assetsFetchResult)

        if let details = changeDetails {
            self.assetsFetchResult = details.fetchResultAfterChanges

            if details.hasIncrementalChanges {
                var removedIndexes: [NSIndexPath]?
                var insertedIndexes: [NSIndexPath]?
                var changedIndexes: [NSIndexPath]?

                if let removed = details.removedIndexes {
                    removedIndexes = createIndexPathsFromIndices(removed)
                }
                if let inserted = details.insertedIndexes {
                    insertedIndexes = createIndexPathsFromIndices(inserted)
                }
                if let changed = details.changedIndexes {
                    changedIndexes = createIndexPathsFromIndices(changed)
                }

                if removedIndexes != nil && changedIndexes != nil {
                    for removedIndex in removedIndexes! {
                        let indexOfAppearanceOfRemovedIndexInChangedIndexes = find(changedIndexes!, removedIndex)
                        if let index = indexOfAppearanceOfRemovedIndexInChangedIndexes {
                            changedIndexes!.removeAtIndex(index)
                        }
                    }
                }

                self.collectionView?.performBatchUpdates({
                    if let removed = removedIndexes {
                        self.collectionView?.deleteItemsAtIndexPaths(removed)
                    }
                    if let inserted = insertedIndexes {
                        self.collectionView?.insertItemsAtIndexPaths(inserted)
                    }
                    if let changed = changedIndexes {
                        self.collectionView?.reloadItemsAtIndexPaths(changed)
                    }
                    if details.hasMoves {
                        changeDetails!.enumerateMovesWithBlock({ fromIndex, toIndex in
                            self.collectionView?.moveItemAtIndexPath(NSIndexPath(forItem: fromIndex, inSection: 0), toIndexPath: NSIndexPath(forItem: toIndex, inSection: 0))
                        })
                    }
                }, completion: nil)
            } else {
                self.collectionView?.reloadData()
            }
        }
    })
}

【讨论】:

  • 更接近,但如果插入仍然会出错......同样的崩溃。我做了另一个功能,就像您使用删除插入删除一样,它运行良好。我遇到的两个问题是它会创建重复项,直到重新加载这些 collectionView 单元格。
  • @Individual11 您是否更新了处理插入崩溃的版本的代码?
【解决方案4】:

所以我在@FernandoEscher 对@batkryu 解决方案的翻译方面做得很好,除了最近重新连接了具有大量更改的 iCloud 照片库的情况。在这种情况下,集合变得完全没有响应并且可能崩溃。核心问题是 photoLibraryDidChange 将在 performBatchUpdates 完成触发之前再次被调用。在 performBatchUpdates 完成之前调用 performBatchUpdates 似乎会降低性能。我怀疑发生崩溃是因为 assetsFetchResults 在动画运行时被修改为之前的值。

太好了,这就是我所做的:

在初始化的其他地方......

self.phPhotoLibChageMutex = dispatch_semaphore_create(1);

_

- (void)photoLibraryDidChange:(PHChange *)changeInstance {
    dispatch_semaphore_wait(self.phPhotoLibChageMutex, DISPATCH_TIME_FOREVER);

    dispatch_async(dispatch_get_main_queue(), ^{

        PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
        if (collectionChanges) {

            self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];

            UICollectionView *collectionView = self.collectionView;
            NSArray *removedPaths;
            NSArray *insertedPaths;
            NSArray *changedPaths;

            if ([collectionChanges hasIncrementalChanges]) {
                NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
                removedPaths = [self indexPathsFromIndexSet:removedIndexes withSection:0];

                NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
                insertedPaths = [self indexPathsFromIndexSet:insertedIndexes withSection:0];

                NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
                changedPaths = [self indexPathsFromIndexSet:changedIndexes withSection:0];

                BOOL shouldReload = NO;

                if (changedPaths != nil && removedPaths != nil) {
                    for (NSIndexPath *changedPath in changedPaths) {
                        if ([removedPaths containsObject:changedPath]) {
                            shouldReload = YES;
                            break;
                        }
                    }
                }

                if (removedPaths.lastObject && ((NSIndexPath *)removedPaths.lastObject).item >= self.assetsFetchResults.count) {
                    shouldReload = YES;
                }

                if (shouldReload) {
                    [collectionView reloadData];
                    [self fixupSelection];
                    dispatch_semaphore_signal(self.phPhotoLibChageMutex);
                } else {
                    [collectionView performBatchUpdates:^{
                        if (removedPaths) {
                            [collectionView deleteItemsAtIndexPaths:removedPaths];
                        }

                        if (insertedPaths) {
                            [collectionView insertItemsAtIndexPaths:insertedPaths];
                        }

                        if (changedPaths) {
                            [collectionView reloadItemsAtIndexPaths:changedPaths];
                        }

                        if ([collectionChanges hasMoves]) {
                            [collectionChanges enumerateMovesWithBlock:^(NSUInteger fromIndex, NSUInteger toIndex) {
                                NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex inSection:0];
                                NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex inSection:0];
                                [collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
                            }];
                        }

                    } completion:^(BOOL finished) {
                        [self fixupSelection];
                        dispatch_semaphore_signal(self.phPhotoLibChageMutex);
                    }];
                }

                [self resetCachedAssets];
            } else {
                [collectionView reloadData];
                [self fixupSelection];
                dispatch_semaphore_signal(self.phPhotoLibChageMutex);
            }
        }else{
            dispatch_semaphore_signal(self.phPhotoLibChageMutex);
        }
    });
}

- (NSArray *)indexPathsFromIndexSet:(NSIndexSet *)indexSet withSection:(int)section {
    if (indexSet == nil) {
        return nil;
    }
    NSMutableArray *indexPaths = [[NSMutableArray alloc] init];

    [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
        [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
    }];

    return indexPaths;
}

【讨论】:

    【解决方案5】:

    我只是在批量更新完成后移动了reloadItemsAtIndexPaths,以修复同时删除和重新加载的崩溃。

    来自changedIndexesPHFetchResultChangeDetails 的文档:

    这些索引是相对于原始提取结果( fetchResultBeforeChanges 属性)在您应用更改后 由removedIndexes 和insertedIndexes 属性描述;什么时候 更新应用的界面,在删除后应用更改,并 插入和移动之前。

    PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
    [collectionView performBatchUpdates:^{ 
            NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
            if ([removedIndexes count]) {
                [collectionView deleteItemsAtIndexPaths:[self indexPathsFromIndexes:removedIndexes withSection:0]];
            }
            NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
            if ([insertedIndexes count]) {
                [collectionView insertItemsAtIndexPaths:[self indexPathsFromIndexes:insertedIndexes withSection:0]];
            }
        } completion:^(BOOL finished) {
            if (finished) {
                // Puting this after removes and inserts indexes fixes a crash of deleting and reloading at the same time.
                // From docs: When updating your app’s interface, apply changes after removals and insertions and before moves.
                NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
                if ([changedIndexes count]) {
                    [collectionView reloadItemsAtIndexPaths:[self indexPathsFromIndexes:changedIndexes withSection:0]];
                }
           }
        }
    

    【讨论】:

    • 还要确保在更改之后添加移动,因为在处理诸如加载图像指示器之类的额外内容时可能会导致一些混淆。
    【解决方案6】:

    最后阅读文档(不是来自 developer.apple.com,而是在 Xcode 中),应用增量更改的正确方法是:

    collectionView.performBatchUpdates({
        // removedIndexes
        // insertedIndexes
    }, completion: {
        // changedIndexes (using collectionView.cellForItem(at: index))
        collectionView.performBatchUpdates({
            // enumerateMoves
        })
    })
    

    在实施这种方法之前,我们在移动项目时遇到了随机崩溃。

    为什么这是正确的?

    removedIndexes”和“insertedIndexes”先行,因为这些索引是相对于原始提取结果的

    "removedIndexes" - "索引与原始提取结果相关(fetchResultBeforeChanges 属性);更新应用界面时,在插入、更改和移动之前应用删除。" "insertedIndexes" - "在您应用了 removedIndexes 属性描述的更改后,索引相对于原始提取结果(fetchResultBeforeChanges 属性);更新您的应用程序界面时,在删除之后以及更改和移动之前应用插入。"

    changedIndexes”不能用“delete”/“insert”处理,因为它们描述了插入/删除之后的项目

    "changedIndexes" - "在您应用了 removedIndexes 和 InsertedIndexes 属性所描述的更改后,这些索引相对于原始提取结果(fetchResultBeforeChanges 属性);当更新您的应用程序的界面,在删除和插入之后以及移动之前应用更改。

    警告 不要在批量更新中将 changedIndexes 直接映射到 UICollectionView 项索引。在 performBatchUpdates(_:completion:) 之后使用这些索引重新配置相应的单元格。 UICollectionView 和 UITableView 期望 changedIndexes 处于 before 状态,而 PhotoKit 在 after 状态提供它们,如果您的应用在更改的同时执行插入和删除,则会导致崩溃。"

    enumerateMoves”是我们最不应该做的事情。

    "enumerateMoves" - "处理程序块中的 toIndex 参数与您应用了 removedIndexes、insertedIndexes 和 changedIndexes 属性描述的更改后的获取结果的状态相关. 因此,如果您使用此方法更新显示提取结果内容的集合视图或类似用户界面,请在处理移动之前更新您的 UI 以反映插入、删除和更改。"

    【讨论】:

      猜你喜欢
      • 2016-01-12
      • 2015-10-01
      • 2021-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多