Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my PHFetchResultChangeDetails have changedIndexes, when all I did was insert at the end?

I'm writing an iOS8 app using the Photos framework. I'm really loving PHFetchResultChangeDetails. But there is something I don't understand: when I save new photos to the camera roll using the following code, then I get back insertions AND changes. I expect only insertions.

To make it concrete:

[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{

PHAssetChangeRequest* newPhotoChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
PHAssetCollectionChangeRequest* albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:_album];
[albumChangeRequest addAssets:[NSArray arrayWithObject:newPhotoChangeRequest.placeholderForCreatedAsset]];
...

The operation is to insert 3 photos using the above code. Before: 5 photos on ipad. After: 8 photos in ipad. Breakpoint on PHFetResultChangeDetails handler shows:

(lldb) po insertionIndexPaths
<__NSArrayM 0x7913be10>(
<NSIndexPath: 0x78e39020> {length = 2, path = 0 - 5},
<NSIndexPath: 0x78e3c910> {length = 2, path = 0 - 6},
<NSIndexPath: 0x78e39480> {length = 2, path = 0 - 7}
)

--> GOOD: makes sense! these are the photos I just inserted (the last 3 indexpaths).

(lldb) po changeIndexPaths
<__NSArrayM 0x78e3c420>(
<NSIndexPath: 0x78e3c7e0> {length = 2, path = 0 - 2},
<NSIndexPath: 0x78e3c7a0> {length = 2, path = 0 - 3},
<NSIndexPath: 0x78e3c7b0> {length = 2, path = 0 - 4}
)

--> DON'T UNDERSTAND: why are these considered "changed"? These are the existing photos on the camera roll... I didn't do anything to them.

Thanks for your help.

UPDATE: it looks like it might have something to do with selection. I neglected to mention, that this happens when there are already some cells selected. What I'm seeing is that when new items are inserted at the end of teh collectionView, then, some selected cells are randomly unselected -- I think the ones that have changeIndexPaths. Wow, that sucks -- I can't see how my code can be doing that! Any hints?

UPDATE2: so, it appears the spurious changeIndexPaths are always the 3 indexPaths directly before the insertion paths (which are always at the end). Why?!

UPDATE3: I'm also seeing crashes in UICollectionView's performBatchUpdates, when the datasource is correctly up-to-date beforehand, if there are both inserts and reloads. For example when the change looks like this:

<PHFetchResultChangeDetails: 0x1742b91a0> 
before=<PHFetchResult: 0x1702b5d20> count=31, 
after=<PHFetchResult: 0x1702b5ea0> count=33, 
hasIncremental=1 deleted=(null), 
inserted=<NSMutableIndexSet: 0x17444aef0>[number of indexes: 2 (in 2 ranges), indexes: (30 32)], 
changed=<NSMutableIndexSet: 0x17444a9b0>[number of indexes: 4 (in 2 ranges), indexes: (27-29 31)], 
hasMoves=0

... then my app crashes in performBatchUpdates with exception:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete item 31 from section 0 which only contains 31 items before the update'

Here is the code of performBatchUpdates, which I copied straight from Apple documentation (!), so I see no reason why changeIndexPaths contains index 31, given that before the inserts, the count=31:

 [self.collectionView performBatchUpdates:^{

      if( deletionIndexPaths.count )
                [self.collectionView deleteItemsAtIndexPaths:deletionIndexPaths];
      if( insertionIndexPaths.count )
                [self.collectionView insertItemsAtIndexPaths:insertionIndexPaths];
      if( changeIndexPaths.count )
                [self.collectionView reloadItemsAtIndexPaths:changeIndexPaths];
      if( moveBlock )
                moveBlock(self.collectionView);
 } ...
like image 750
xaphod Avatar asked Dec 04 '14 09:12

xaphod


1 Answers

Re: Update 3

Despite what the sample code says, changedIndexes cannot be used in performBatchUpdates like this.

The indexes PHFetchResultChangeDetails.changedIndexes are relative to the original fetch result after the indexes in removedIndexes are removed, and after the new indexes in insertedIndexes are added.

The UITableView and UICollectionView APIs however require that reloadItems* methods, when called in a batch update, have indexes from before any other changes.

To fix this call the reload and move entries outside batch updates, e.g.:

    [self.collectionView performBatchUpdates:^{

        if( deletionIndexPaths.count )
            [self.collectionView deleteItemsAtIndexPaths:deletionIndexPaths];
        if( insertionIndexPaths.count )
            [self.collectionView insertItemsAtIndexPaths:insertionIndexPaths];
    } ... ]
    if( changeIndexPaths.count )
        [self.collectionView reloadItemsAtIndexPaths:changeIndexPaths];
    if( moveBlock )
        moveBlock(self.collectionView);
like image 128
Scott James Remnant Avatar answered Oct 24 '22 13:10

Scott James Remnant