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?
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.
There is a super simple solution.
Create a fetch result without any specific options.
// ...
let options = PHFetchOptions()
fetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: options)
if fetchResult.count > 0 {
collectionView.reloadData()
}
// ...
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. :]
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()
}
...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With