I'm having a problem trying to change my data source after a PHChange
event occurs. I'm using an NSMutableOrderedSet
as my data source type. When a PHChange
happens, it can give you back three possible indexes: removedIndexes
, insertedIndexes
, and changedIndexes
.
The problem is I'm not sure how I can go about reflecting these changes in the prior data source. Could someone shed some light on how I could convert the indexes given in the PHChange
to update my recentsCollectionDataSource
. My UICollectionView
uses recentsCollectionDataSource
as its data source.
Right now I'm thinking the only way is to manually enumerate and build my own changed indexes instead of using the PHFetchResultChangeDetails
ones.
I'd appreciate any help offered. Any recommendations for alternative ways of making my data source are also welcome.
Code:
@property (nonatomic, strong) PHFetchResult *assetsFetchResult;
@property (nonatomic, strong) NSMutableOrderedSet *recentsCollectionDataSource;
- (void)setup
{
self.recentsCollectionDataSource = [[NSMutableOrderedSet alloc]init];
self.assetsFetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum | PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
for (PHAssetCollection *sub in self.assetsFetchResult)
{
PHFetchResult *assetsInCollection = [PHAsset fetchAssetsInAssetCollection:sub options:nil];
for (PHAsset *asset in assetsInCollection)
{
[self.recentsCollectionDataSource addObject:asset];
}
}
if (self.recentsCollectionDataSource.count > 0)
{
NSArray *array = [self.recentsCollectionDataSource sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]];
self.recentsCollectionDataSource = [[NSMutableOrderedSet alloc]initWithArray:array];
}
}
- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResult];
if (collectionChanges)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^
{
self.assetsFetchResult = [collectionChanges fetchResultAfterChanges];
if (!collectionChanges.hasIncrementalChanges || collectionChanges.hasMoves)
{
dispatch_async(dispatch_get_main_queue(),^
{
[self.recentsCollectionView reloadData];
});
}
else
{
dispatch_async(dispatch_get_main_queue(),^
{
[self.recentsCollectionView performBatchUpdates:^
{
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
// Need to update our old recentsCollectionDataSource
// somehow, otherwise we get a crash.
if (removedIndexes.count)
{
[self.recentsCollectionView deleteItemsAtIndexPaths:[removedIndexes indexPathsFromIndexesWithSection:0]];
}
if (insertedIndexes.count)
{
[self.recentsCollectionView insertItemsAtIndexPaths:[insertedIndexes indexPathsFromIndexesWithSection:0]];
}
if (changedIndexes.count)
{
[self.recentsCollectionView reloadItemsAtIndexPaths:[changedIndexes indexPathsFromIndexesWithSection:0]];
}
}completion:NULL];
});
}
});
}
}
@implementation NSIndexSet (Convenience)
- (NSArray *)indexPathsFromIndexesWithSection:(NSUInteger)section
{
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop)
{
[indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
}];
return indexPaths;
}
@end
I've seemed to solve it (at least for now). I built my own removed, inserted, and changed indexes respectively, which isn't as efficient as the built in PHFetchResultChangeDetails
ones, but I don't believe there's another way to do this. It takes about 4 seconds to chew through 2600 photos on an iPhone 5 with this code, so not as bad as it could be.
I've noticed what may be a bug with PHFetchResultChangeDetails
, it doesn't put Favorite changes in the change detail, so you might as well perform the entire collection fetch again if you want to remain visually consistent in your UI.
Code:
- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResult];
if (collectionChanges)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^
{
self.assetsFetchResult = [collectionChanges fetchResultAfterChanges];
NSMutableOrderedSet *newSet = [[NSMutableOrderedSet alloc]init];
for (PHAssetCollection *sub in self.assetsFetchResult)
{
PHFetchResult *assetsInCollection = [PHAsset fetchAssetsInAssetCollection:sub options:nil];
for (PHAsset *asset in assetsInCollection)
{
[newSet addObject:asset];
}
}
NSMutableIndexSet *removedIndexes = [[NSMutableIndexSet alloc]init];
NSMutableIndexSet *insertedIndexes = [[NSMutableIndexSet alloc]init];
NSMutableIndexSet *changedIndexes = [[NSMutableIndexSet alloc]init];
NSMutableArray *objectsToInsert = [[NSMutableArray alloc]init];
if (newSet.count > 0)
{
NSArray *array = [newSet sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]];
newSet = [[NSMutableOrderedSet alloc]initWithArray:array];
for (id obj in newSet)
{
NSUInteger idx = [self.recentsCollectionDataSource indexOfObject:obj];
if (idx == NSNotFound)
{
[objectsToInsert addObject:obj];
[insertedIndexes addIndex:[newSet indexOfObject:obj]];
}
else
{
NSUInteger objIdx = [newSet indexOfObject:obj];
PHAsset *oldAssetToCompare = (PHAsset *)[self.recentsCollectionDataSource objectAtIndex:idx];
PHAsset *plausibleNewAssetToCompare = (PHAsset *)[newSet objectAtIndex:objIdx];
if (![oldAssetToCompare isEqual:plausibleNewAssetToCompare])
{
[changedIndexes addIndex:idx];
}
}
}
for (id obj in self.recentsCollectionDataSource)
{
NSUInteger idx = [newSet indexOfObject:obj];
if (idx == NSNotFound)
{
[removedIndexes addIndex:[self.recentsCollectionDataSource indexOfObject:obj]];
}
else
{
NSUInteger objIdx = [self.recentsCollectionDataSource indexOfObject:obj];
PHAsset *oldAssetToCompare = (PHAsset *)obj;
PHAsset *plausibleNewAssetToCompare = (PHAsset *)[newSet objectAtIndex:idx];
if (![oldAssetToCompare isEqual:plausibleNewAssetToCompare])
{
[changedIndexes addIndex:objIdx];
}
}
}
}
if (removedIndexes.count > 0)
{
[self.recentsCollectionDataSource removeObjectsAtIndexes:removedIndexes];
}
if (insertedIndexes.count > 0)
{
[self.recentsCollectionDataSource insertObjects:objectsToInsert atIndexes:insertedIndexes];
}
if (!collectionChanges.hasIncrementalChanges || collectionChanges.hasMoves)
{
dispatch_async(dispatch_get_main_queue(),^
{
[self.recentsCollectionView reloadData];
});
}
else
{
dispatch_async(dispatch_get_main_queue(),^
{
[self.recentsCollectionView performBatchUpdates:^
{
if (removedIndexes.count > 0)
{
[self.recentsCollectionView deleteItemsAtIndexPaths:[removedIndexes indexPathsFromIndexesWithSection:0]];
}
if (insertedIndexes.count > 0)
{
[self.recentsCollectionView insertItemsAtIndexPaths:[insertedIndexes indexPathsFromIndexesWithSection:0]];
}
if (changedIndexes.count > 0)
{
[self.recentsCollectionView reloadItemsAtIndexPaths:[changedIndexes indexPathsFromIndexesWithSection:0]];
}
}completion:NULL];
});
}
});
}
}