本日、再現できました。これを行うには、次のことが必要です。
- 変更をリッスンしているアプリを開きます
- 写真アプリを開き、iCloud 共有アルバムから一連の写真をフォト ライブラリに保存します
- 写真アプリに移動し、それらの写真の一部を削除します
- もう一度 iCloud 共有アルバムに移動し、削除した写真の一部を再度保存します。この状態が発生することがわかります。
https://developer.apple.com/library/ios/documentation/Photos/Reference/PHPhotoLibraryChangeObserver_Protocol/ :
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
}
スイフト 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)
}
}