Skip to content

Commit

Permalink
Fix handling of deleted photos on iOS (#1556)
Browse files Browse the repository at this point in the history
* Fix crash on iOS when images are deleted

Fixes a crash on iOS when a displayed image is deleted in the
background:

    NSInternalInconsistencyException
    reason: 'attempt to delete and reload the same index path <snip>'

According to
https://developer.apple.com/documentation/photokit/phfetchresultchangedetails?language=objc

    ...changedIndexes can't be used safely inside
    performBatchUpdates:completion: Instead, use changedIndexes after and
    outside the performBatchUpdates:completion: call...

* Update selection when assets are removed from collection

If a selected photo is deleted from the photo library, it needs to be
removed from `selectedAssets` or the UI will hang when the user taps
"Done". Also, the UI needs to be updated to reflect the new selection.

* Update counts in footer when collection changes

When the number of photos or videos in the collection changes, update
the footer to reflect the new counts.
  • Loading branch information
brsaylor2 committed Nov 13, 2023
1 parent 415834e commit e776878
Showing 1 changed file with 89 additions and 44 deletions.
133 changes: 89 additions & 44 deletions ios/QBImagePicker/QBImagePicker/QBAssetsViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -417,15 +417,31 @@ - (void)photoLibraryDidChange:(PHChange *)changeInstance
if ([insertedIndexes count]) {
[self.collectionView insertItemsAtIndexPaths:[insertedIndexes qb_indexPathsFromIndexesWithSection:0]];
}

NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
if ([changedIndexes count]) {
[self.collectionView reloadItemsAtIndexPaths:[changedIndexes qb_indexPathsFromIndexesWithSection:0]];
}
} completion:NULL];

NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
if ([changedIndexes count]) {
[self.collectionView reloadItemsAtIndexPaths:[changedIndexes qb_indexPathsFromIndexesWithSection:0]];
}
}

[self resetCachedAssets];

// Update the selection to remove any assets that have been removed from the collection
NSMutableSet *removedAssets = [NSMutableSet new];
for (PHAsset *asset in self.imagePickerController.selectedAssets) {
if(![self.fetchResult containsObject:asset]) {
[removedAssets addObject:asset];
}
}
[self removeAssetsFromSelection:removedAssets];

// Update the footer to show the current photo/video counts
NSArray<UICollectionReusableView *> *footers =
[self.collectionView visibleSupplementaryViewsOfKind:UICollectionElementKindSectionFooter];
if (footers.count) {
[self updateFooterView:footers[0]];
}
}
});
}
Expand Down Expand Up @@ -507,57 +523,60 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
UICollectionReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:@"FooterView"
forIndexPath:indexPath];
[self updateFooterView:footerView];

// Number of assets
UILabel *label = (UILabel *)[footerView viewWithTag:1];
return footerView;
}

NSBundle *bundle = self.imagePickerController.assetBundle;
NSUInteger numberOfPhotos = [self.fetchResult countOfAssetsWithMediaType:PHAssetMediaTypeImage];
NSUInteger numberOfVideos = [self.fetchResult countOfAssetsWithMediaType:PHAssetMediaTypeVideo];
return nil;
}

switch (self.imagePickerController.mediaType) {
case QBImagePickerMediaTypeAny:
{
NSString *format;
if (numberOfPhotos == 1) {
if (numberOfVideos == 1) {
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photo-and-video", @"QBImagePicker", bundle, nil);
} else {
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photo-and-videos", @"QBImagePicker", bundle, nil);
}
} else if (numberOfVideos == 1) {
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photos-and-video", @"QBImagePicker", bundle, nil);
- (void)updateFooterView:(UICollectionReusableView *)footerView {
// Number of assets
UILabel *label = (UILabel *)[footerView viewWithTag:1];

NSBundle *bundle = self.imagePickerController.assetBundle;
NSUInteger numberOfPhotos = [self.fetchResult countOfAssetsWithMediaType:PHAssetMediaTypeImage];
NSUInteger numberOfVideos = [self.fetchResult countOfAssetsWithMediaType:PHAssetMediaTypeVideo];

switch (self.imagePickerController.mediaType) {
case QBImagePickerMediaTypeAny:
{
NSString *format;
if (numberOfPhotos == 1) {
if (numberOfVideos == 1) {
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photo-and-video", @"QBImagePicker", bundle, nil);
} else {
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photos-and-videos", @"QBImagePicker", bundle, nil);
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photo-and-videos", @"QBImagePicker", bundle, nil);
}

label.text = [NSString stringWithFormat:format, numberOfPhotos, numberOfVideos];
} else if (numberOfVideos == 1) {
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photos-and-video", @"QBImagePicker", bundle, nil);
} else {
format = NSLocalizedStringFromTableInBundle(@"assets.footer.photos-and-videos", @"QBImagePicker", bundle, nil);
}
break;

case QBImagePickerMediaTypeImage:
{
NSString *key = (numberOfPhotos == 1) ? @"assets.footer.photo" : @"assets.footer.photos";
NSString *format = NSLocalizedStringFromTableInBundle(key, @"QBImagePicker", bundle, nil);

label.text = [NSString stringWithFormat:format, numberOfPhotos];
}
break;
label.text = [NSString stringWithFormat:format, numberOfPhotos, numberOfVideos];
}
break;

case QBImagePickerMediaTypeVideo:
{
NSString *key = (numberOfVideos == 1) ? @"assets.footer.video" : @"assets.footer.videos";
NSString *format = NSLocalizedStringFromTableInBundle(key, @"QBImagePicker", bundle, nil);
case QBImagePickerMediaTypeImage:
{
NSString *key = (numberOfPhotos == 1) ? @"assets.footer.photo" : @"assets.footer.photos";
NSString *format = NSLocalizedStringFromTableInBundle(key, @"QBImagePicker", bundle, nil);

label.text = [NSString stringWithFormat:format, numberOfVideos];
}
break;
label.text = [NSString stringWithFormat:format, numberOfPhotos];
}
break;

return footerView;
}
case QBImagePickerMediaTypeVideo:
{
NSString *key = (numberOfVideos == 1) ? @"assets.footer.video" : @"assets.footer.videos";
NSString *format = NSLocalizedStringFromTableInBundle(key, @"QBImagePicker", bundle, nil);

return nil;
label.text = [NSString stringWithFormat:format, numberOfVideos];
}
break;
}
}


Expand Down Expand Up @@ -653,6 +672,32 @@ - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndex
}
}

- (void)removeAssetsFromSelection:(NSSet *)assets
{
if (assets.count == 0) {
return;
}

QBImagePickerController *imagePickerController = self.imagePickerController;
NSMutableOrderedSet *selectedAssets = imagePickerController.selectedAssets;

// Remove assets from set
[selectedAssets minusSet:assets];

self.lastSelectedItemIndexPath = nil;

[self updateDoneButtonState];

if (self.imagePickerController.showsNumberOfSelectedAssets) {
[self updateSelectionInfo];

if (selectedAssets.count == 0) {
// Hide toolbar
[self.navigationController setToolbarHidden:YES animated:YES];
}
}
}


#pragma mark - UICollectionViewDelegateFlowLayout

Expand Down

0 comments on commit e776878

Please sign in to comment.