Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHFetchOptions for PHAsset in same sort order as Photos.app

I'm using the new PHFetchOptions' sortDescriptors for getting PHAssets from the camera roll. There seem to only be two keys to sort on: creationDate and modificationDate, neither of which are the same as what you see in the Photos.app.

Am I missing something? How can we get this to work?

like image 597
Art C Avatar asked Mar 13 '15 00:03

Art C


3 Answers

I have used the following code to get the list of smart albums which includes the "Camera Roll":

// get the smart albums
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
for (NSInteger i = 0; i < smartAlbums.count; i++) {
    PHAssetCollection *assetCollection = smartAlbums[i];
    // Then use the following to get the photo images:
    PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
    }

The default sort order is the existing order of assets in collections including the "Camera Roll". Passing "nil" for options gets the default. You can reorder the images in the camera roll and this code will reflect the changes.

like image 117
McInvale Avatar answered Nov 19 '22 13:11

McInvale


There is a super simple solution.

  1. Create a fetch result without any specific options.

    // ...
    
    let options = PHFetchOptions()
    fetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: options)
    
    if fetchResult.count > 0 {
      collectionView.reloadData()
    }
    
    // ...
    
  2. When you try to load a certain asset just use a reversed index.

    // ...
    
    let reversedIndex = fetchResult.count - indexPath.item - 1
    let asset = fetchResult[reversedIndex] as! PHAsset
    
    // ...
    

That way you'll get exactly the same flow order as in the Photo.app.

Hope you find it helpful. :]

like image 33
mozharovsky Avatar answered Nov 19 '22 13:11

mozharovsky


Follow up on @Majotron's answer, I want to fetch all user's photos in the exact order as the All Photos albums in Photos app, and with only image assets.

Here is how I set up my PHFetchOptions:

let allPhotosOption = PHFetchOptions()
// Fetch only image asset
allPhotosOption.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
// Get All Photos album collection
let userLibrary = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil).firstObject
allPhotosResult = PHAsset.fetchAssets(in: userLibrary!, options: allPhotosOption)

And obtain specific asset using reversed index inside your TableView/CollectionView:

let asset = allPhotosResult.object(at: allPhotosResult.count - indexPath.item - 1)

Also be careful if you're listening to photo library changes and rearrange your view accordingly. Obtain reversed index using fetch result before changes when changes are related to removal, using .fetchResultAfterChanges otherwise.

Here is how I handle it:

func photoLibraryDidChange(_ changeInstance: PHChange) {

    ... 

    DispatchQueue.main.sync {
        let originalCount = fetchResult.count
        fetchResult = changes.fetchResultAfterChanges
        if changes.hasIncrementalChanges {

            guard let collectionView = self.collectionView else { return }

            collectionView.performBatchUpdates({

                if let removed = changes.removedIndexes, removed.count > 0 {
                    collectionView.deleteItems(at: removed.map({ IndexPath(item: originalCount - $0 - 1, section: 0) }))
                }
                if let inserted = changes.insertedIndexes, inserted.count > 0 {
                    collectionView.insertItems(at: inserted.map({ IndexPath(item: fetchResult.count - $0 - 1, section: 0) }))
                }
            })
            // Avoid deleting and reloading same index path at one update
            collectionView.performBatchUpdates({
                if let changed = changes.changedIndexes, changed.count > 0 {
                    collectionView.reloadItems(at: changed.map({ IndexPath(item: fetchResult.count - $0 - 1, section: 0) }))
                }
                changes.enumerateMoves { fromIndex, toIndex in
                    collectionView.moveItem(at: IndexPath(item: fetchResult.count - $0 - 1, section: 0),
                                            to: IndexPath(item: fetchResult.count - $0 - 1, section: 0))
                }
            })

        } else {
            collectionView!.reloadData()
        }

        ...

    }
}
like image 1
kubrick G Avatar answered Nov 19 '22 13:11

kubrick G