I'm building Gallery app like a iOS standard Photos App. (Swift 4.1)
I want to fetch the thumbnails, titles, and total number of images that I can see when I launch the standard photo app.
The Photos framework seems more complex than I thought.
It is not easy to find a way to explain why, and what procedures should be approached.
Can you tell me about this?
The minimum number of steps to achieve what you are asking is:
// import the framework
import Photos
// get the albums list
let albumList = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: nil)
// you can access the number of albums with
albumList.count
// individual objects with
let album = albumList.object(at: 0)
// eg. get the name of the album
album.localizedTitle
// get the assets in a collection
func getAssets(fromCollection collection: PHAssetCollection) -> PHFetchResult<PHAsset> {
let photosOptions = PHFetchOptions()
photosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
photosOptions.predicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.image.rawValue)
return PHAsset.fetchAssets(in: collection, options: photosOptions)
}
// eg.
albumList.enumerateObjects { (coll, _, _) in
let result = self.getAssets(fromCollection: coll)
print("\(coll.localizedTitle): \(result.count)")
}
// Now you can:
// access the count of assets in the PHFetchResult
result.count
// get an asset (eg. in a UITableView)
let asset = result.object(at: indexPath.row)
// get the "real" image
PHCachingImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: nil) { (image, _) in
// do something with the image
}
I also suggest to take a look at the Apple sample code for the Photos framework, is not hard to follow, together with the Photos framework documentation.
Related
I am trying to get Video without open Gallery or UIImagePickerControllerlike I got success in getting images without open gallery. Is there any way to get files without open UIImagePickerController.
Can someone please explain to me how to get files without open UIImagePickerController. Any help would be greatly appreciated.
Thanks in advance.
For example, using the following code, you can get the latest user video:
import Photos
let options = PHFetchOptions()
options.fetchLimit = 1
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
options.sortDescriptors = [sortDescriptor]
let fetchResult = PHAsset.fetchAssets(with: .video, options: options)
if fetchResult.count == 0 {
// user has no video...
return
}
let asset = fetchResult[0]
let requestOptions = PHVideoRequestOptions()
let manager = PHImageManager.default()
Then, if you want to get the video itself, use the following code:
manager.requestAVAsset(forVideo: asset, options: requestOptions, resultHandler: { oAsset, oAudioMix, oDict in
if let urlAsset = oAsset as? AVURLAsset {
let url = urlAsset.url
// use URL to get file content
}
})
Otherwise, if you just want to play the video, use the following code:
manager.requestPlayerItem(forVideo: asset, options: requestOptions, resultHandler: { oPlayerItem, oDict in
// do something with oPlayerItem
})
For more information you can read this
This question already has answers here:
Swift: asynchronously loading and displaying photos
(2 answers)
Closed 3 years ago.
When I am trying to fetch an 10 images from my gallery it takes very long until I get it.
Why is the image fetching async?
If I change the requestOptions.deliveryMode to .fastFormat it gets fast but I lose a looooot of quality
What is the best that I can do?
func fetchPhotos() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.fetchLimit = 10
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
if fetchResult.count > 0 {
let totalImageCountNeeded = 10 // <-- The number of images to fetch
fetchPhotoAtIndex(0, totalImageCountNeeded, fetchResult)
}
}
func fetchPhotoAtIndex(_ index:Int, _ totalImageCountNeeded: Int, _ fetchResult: PHFetchResult<PHAsset>) {
let requestOptions = PHImageRequestOptions()
requestOptions.deliveryMode = .highQualityFormat
PHImageManager.default().requestImage(for: fetchResult.object(at: index) as PHAsset, targetSize: view.frame.size, contentMode: PHImageContentMode.aspectFill, options: requestOptions, resultHandler: { (image, _) in
if let image = image {
// Add the returned image to your array
self.images += [image]
}
if index + 1 < fetchResult.count && self.images.count < totalImageCountNeeded {
self.fetchPhotoAtIndex(index + 1, totalImageCountNeeded, fetchResult)
} else {
print("Completed array: \(self.images)")
self.collectionView.reloadData()
}
})
}
I just tested your code and it took about a second to load 50 images, check if there something else in your code that could be slowing downs this process.
If your intentions is to have all the images loaded as soon as you present the View Controller consider loading the images on the previous screen.
The request is async because it needs to use the network. Async just means it takes time to perform, outside of the syncronous execution of the application's threads. Synchronous would be it executes the code in sync with the application thread the code is running on. Here, the thread must wait for the network request to finish.
You'll have a tradeoff between quality and speed. Obviously, the better quality the image, the larger it is, and therefore it takes longer to download.
I'd suggest you download smaller chunks of photos, or rethink your UI such that less images are shown at any one time. You could instead of 10 show 3 images, and preload the next 3 with pagination.
Is it possible to fetch all PHAssets that don't belong to any PHAssetColletion? Basically what I want to achieve is to get all photos that are not sorted to any album.
I know I can do this:
let photos = PHAsset.fetchAssets(with: PHFetchOptions())
var photosWithoutAlbum: [PHAsset] = []
photos.enumerateObjects({
asset, index, stop in
let albums = PHAssetCollection.fetchAssetCollectionsContaining(asset, with: .album, options: PHFetchOptions())
if albums.count == 0 {
photosWithoutAlbum.append(asset)
}
})
But maybe there is a better/faster way using PHFetchOptions or different fetch method?
In iOS PhotoKit, I can fetch all non-empty albums like this:
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "estimatedAssetCount > 0")
let albumFetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: albumFetchOptions)
albumFetchResult.enumerateObjects({ (collection, _, _) in
// Do something with the album...
})
Then I can get only photos from the album like this:
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetResourceType.photo.rawValue)
let fetchResults = PHAsset.fetchAssets(in: collection, options: fetchOptions)
But the first part can give me albums with only videos, which means that after I apply the predicate to the second part, the album will be empty. Is there a way to filter out those albums in the first part, before I start using them?
It seems that collections cannot be filtered like this without also fetching the items in the collections. See the docs for available fetch options; none allow filtering by number of a specific type of media.
The way I would achieve this is by fetching all albums created by the user, and then fetching the assets from the albums with a predicate that returns only images.
So to put it in code:
var userCollections: PHFetchResult<PHAssetCollection>!
// Fetching all PHAssetCollections with at least some media in it
let options = PHFetchOptions()
options.predicate = NSPredicate(format: "estimatedAssetCount > 0")
// Performing the fetch
userCollections = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: options)
Next, fetch the assets from a collection that are images by specifying a predicate:
// Getting the specific collection (I assumed to use a tableView)
let collection = userCollections[indexPath.row]
let optionsToFilterImage = PHFetchOptions()
optionsToFilterImage.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.Image.rawValue)
// Fetching the asset with the predicate to filter just images
let justImages = PHAsset.fetchAssets(in: collection, options: optionsToFilterImage)
Lastly, count the number of images:
if justImages.count > 0 {
// Display it
} else {
// The album has no images
}
I want to list all photos from "My Photo Stream", here is my code:
private func fetchAssetCollection(){
let result = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumMyPhotoStream, options: nil)
result.enumerateObjects({ (collection, index, stop) in
if let albumName = collection.localizedTitle {
print("Album => \(collection.localIdentifier), \(collection.estimatedAssetCount), \(albumName) ")
}
let assResult = PHAsset.fetchAssets(in: collection, options: nil)
let options = PHImageRequestOptions()
options.resizeMode = .exact
let scale = UIScreen.main.scale
let dimension = CGFloat(78.0)
let size = CGSize(width: dimension * scale, height: dimension * scale)
assResult.enumerateObjects({ (asset, index, stop) in
print("index \(index)")
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: options) { (image, info) in
if let name = asset.originalFilename {
print("photo \(name) \(index) \(asset.localIdentifier)")
}
}
})
})
}
extension PHAsset {
var originalFilename: String? {
var fname:String?
if #available(iOS 9.0, *) {
let resources = PHAssetResource.assetResources(for: self)
if let resource = resources.first {
fname = resource.originalFilename
}
}
if fname == nil {
// this is an undocumented workaround that works as of iOS 9.1
fname = self.value(forKey: "filename") as? String
}
return fname
}
}
it works, but the problem is that it print duplicated record.
It prints 329*2 records but actually I have 329 photos in my "My Photo stream".
photo IMG_0035.JPG 10 0671E1F3-CB7C-459E-8111-FCB381175F29/L0/001
photo IMG_0035.JPG 10 0671E1F3-CB7C-459E-8111-FCB381175F29/L0/001
......
From the documentation for PHImageManager requestImage:
By default, this method executes asynchronously. If you call it from a background thread you may change the isSynchronous property of the options parameter to true to block the calling thread until either the requested image is ready or an error occurs, at which time Photos calls your result handler.
For an asynchronous request, Photos may call your result handler block more than once. Photos first calls the block to provide a low-quality image suitable for displaying temporarily while it prepares a high-quality image. (If low-quality image data is immediately available, the first call may occur before the method returns.) When the high-quality image is ready, Photos calls your result handler again to provide it. If the image manager has already cached the requested image at full quality, Photos calls your result handler only once. The PHImageResultIsDegradedKey key in the result handler’s info parameter indicates when Photos is providing a temporary low-quality image.
So either make the request synchronous or check the PHImageResultIsDegradedKey value from the info dictionary to see if this instance of the image is the one you actually wish to keep or ignore.