Ted*_*Ted 14 objective-c collectionview ios ios8 photokit
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];
}
});
}
Run Code Online (Sandbox Code Playgroud)
来源:https://developer.apple.com/devcenter/download.action?path=/wwdc_2014/wwdc_2014_sample_code/exampleappusingphotosframework.zip
我不能复制这个问题.可能是什么问题呢?非常感谢!
小智 18
我今天能够重现这一点.要做到这一点,你需要:
我发现一个更新的代码似乎更好地处理更新行为:https: //developer.apple.com/library/ios/documentation/Photos/Reference/PHPhotoLibraryChangeObserver_Protocol/
但是它仍然没有处理这种情况,也没有当要删除的索引更大时(即由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'尝试从更新前仅包含9个项目的第0部分删除第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
}
Run Code Online (Sandbox Code Playgroud)
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)
}
}
Run Code Online (Sandbox Code Playgroud)
kga*_*dis 11
我刚刚reloadItemsAtIndexPaths完成批量更新后移动了,以便同时修复删除和重新加载的崩溃.
从文档changedIndexes的PHFetchResultChangeDetails:
在应用removedIndexes和insertedIndexes属性描述的更改后,这些索引相对于原始获取结果(fetchResultBeforeChanges属性); 更新应用程序界面时,在删除和插入之后以及移动之前应用更改.
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]];
}
}
}
Run Code Online (Sandbox Code Playgroud)
我在Objective-C的batkryu答案中实现了代码.
- (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;
}
Run Code Online (Sandbox Code Playgroud)
最后阅读文档(不是来自 developer.apple.com,而是在 Xcode 中),应用增量更改的正确方法是:
collectionView.performBatchUpdates({
// removedIndexes
// insertedIndexes
}, completion: {
// changedIndexes (using collectionView.cellForItem(at: index))
collectionView.performBatchUpdates({
// enumerateMoves
})
})
Run Code Online (Sandbox Code Playgroud)
在实施这种方法之前,我们在移动项目时发生了随机崩溃。
为什么这是正确的?
“ removedIndexes ”和“ insertedIndexes ”先走了,因为这个指标是相对于原来的提取结果
" removedIndexes " - "索引相对于原始提取结果(fetchResultBeforeChanges 属性);更新应用程序界面时,在插入、更改和移动之前应用删除。” "insertedIndexes" - "在您应用了由 removeIndexes 属性描述的更改之后,索引是相对于原始获取结果(fetchResultBeforeChanges 属性)的;更新应用程序界面时,在删除之后以及更改和移动之前应用插入。 ”
“ changedIndexes ”不能用“delete”/“insert”处理,因为它们描述了插入/删除后的项目
" changedIndexes " - "在您应用了由removedIndexes 和insertIndexes 属性描述的更改之后,这些索引是相对于原始获取结果(fetchResultBeforeChanges 属性)的;更新应用程序界面时,在删除和插入之后以及移动之前应用更改。
警告 不要在批量更新中将 changedIndexes 直接映射到 UICollectionView 项目索引。在 performBatchUpdates(_:completion:) 之后使用这些索引重新配置相应的单元格。UICollectionView 和 UITableView 期望 changedIndexes 处于 before 状态,而 PhotoKit 在 after 状态提供它们,如果您的应用程序在更改的同时执行插入和删除操作,则会导致崩溃。”
“ enumerateMoves ”是我们应该做的最后一件事。
" enumerateMoves " - "处理程序块中的 toIndex 参数与在应用了由 removeIndexes、insertIndexes 和 changedIndexes 属性描述的更改后获取结果的状态相关。因此,如果您使用此方法更新集合视图或显示提取结果内容的类似用户界面,在处理移动之前更新您的 UI 以反映插入、删除和更改。 ”