How to set an offset to PHAsset.fetchAssets? - ios

This is my code to fetch images from the device gallery.
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
//loading all the images
requestOptions.isSynchronous = false
//Quality of the image
requestOptions.deliveryMode = .highQualityFormat
//requestOptions.resizeMode = .fast
//Sorted images
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 500
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
DispatchQueue.global(qos: .background).async {
if fetchResult.count > 0 {
DispatchQueue.main.async {
self.showProgress()
}
for i in 0..<fetchResult.count{
imgManager.requestImage(for: fetchResult.object(at: i) , targetSize: CGSize(width: self.selectedImage.frame.width, height:self.selectedImage.frame.height), contentMode: .aspectFill, options: requestOptions, resultHandler: {
image, error in
if(image == nil){
}else{
self.imageArray.append(image!)
}
})
}
DispatchQueue.main.async {
self.hideProgress()
self.photosCollectionView.reloadData()
}
}else{
self.hideProgress()
self.showDefaultAlert(title: "Alert", message: "Could not fetch images from the gallery")
}
I get memory issues when there are about 1500+ images in the device. I have figured out how to set a limit of how to i get the next 500 images when collection view is scrolled to the bottom? Any help would be much appreciated.

What is the problem you fixing? I think, heavy operation is in requestImage, you can try
fetchResult.objects(at: IndexSet(0...500))
fetchResult.objects(at: IndexSet(501...1000))
etc.
or may be add predicate
fetchOptions.predicate = NSPredicate(format: "#NOT(localIdentifier IN %#)", alreadyGettedIdentifiers)
for i in 0..<fetchResult.count{
let fetchResult = fetchResult.object(at: i)
alreadyGettedIdentifiers.append(fetchResult.localIdentifier)
PHImageManager.default().requestImage(for: fetchResult, targetSize: CGSize(width: 200, height:200), contentMode: .aspectFill, options: requestOptions, resultHandler: {
image, error in
if(image == nil){
}else{
self.imageArray.append(image!)
}
})
}

Related

swift 5, how do I load over 7000~ image in collectionView?

I am using UICollectionView for an album. I got the picture to load on iPhone 12. I got it to load about 100 pictures but if there are over 3000 pictures when I scroll the UICollectionView down it is very slow and sometimes the app stops.
I am using this code to get the pictures.
func grabPhotos(){
imageArray = []
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let assetsFetchResult = (collection == nil) ? PHAsset.fetchAssets(with: .image, options: options) : PHAsset.fetchAssets(in: collection!, options: nil)
assets = assetsFetchResult.objects(at: IndexSet(integersIn: Range(NSMakeRange(0, assetsFetchResult.count))!))
let imgManager=PHImageManager.default()
let requestOptions=PHImageRequestOptions()
requestOptions.isSynchronous=true
requestOptions.deliveryMode = .highQualityFormat
requestOptions.resizeMode = .exact
requestOptions.version = .original
requestOptions.isNetworkAccessAllowed = true
if assetsFetchResult.count > 0 {
for i in 0..<assetsFetchResult.count{
imgManager.requestImage(for: assetsFetchResult.object(at: i) as PHAsset, targetSize: CGSize(width:150, height: 200) ,contentMode: .aspectFill, options: requestOptions, resultHandler: { (image, error) in
if image != nil {
DispatchQueue.main.async(execute: {
self.imageArray.append(image!)
})
}
})
}
} else {
print("You got no photos.")
}
DispatchQueue.main.async {
print("This is run on the main queue, after the previous code in outer block")
self.categoryView.reloadData()
self.activityIndicator.stopAnimating()
}
}

Swift iOS: -How to exclude .livePhotos from inclusion in the PHFetchOptions?

Following #matt's code when using the UIImagePicker, I can prevent the user from picking a .livePhoto once an image is choosen using:
let asset = info[UIImagePickerControllerPHAsset] as? PHAsset
if asset?.playbackStyle == .livePhoto {
// alert user this photo isn't a possibility
}
When using the PHFetchOptions how can I prevent them from being shown instead of filtering them out inside the enumerateObjects callback?
fileprivate func fetchPhotos() {
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 1000
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
let allPhotos = PHAsset.fetchAssets(with: .video, options: fetchOptions)
allPhotos.enumerateObjects {
[weak self] (asset, count, stop) in
if asset.playbackStyle == .livePhoto {
return
}
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 350, height: 350)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: {
[weak self] (image, info) in
if let image = image {
self?.tableData.append(image)
}
if count == allPhotos.count - 1 {
self?.collectionView.reloadData()
}
})
}
}
You can use the predicate property of PHFetchOptions. Setup the predicate to fail if the mediaSubtype attribute of the asset indicates it is a live photo.
fetchOptions.predicate = NSPredicate(format: "(mediaSubtype & %ld) == 0", PHAssetMediaSubtype.PhotoLive.rawValue)

Swift 3 - Fetch All Photos From Library

I've tried to fetch photos from the library. It works, but I just got 3 photos from 9 photos from the library. Here's my code:
let options = PHFetchOptions()
let userAlbums = PHAssetCollection.fetchAssetCollections(with: PHAssetCollectionType.album, subtype: PHAssetCollectionSubtype.any, options: options)
let userPhotos = PHAsset.fetchKeyAssets(in: userAlbums.firstObject!, options: nil)
let imageManager = PHCachingImageManager()
userPhotos?.enumerateObjects({ (object: AnyObject!, count: Int, stop: UnsafeMutablePointer) in
if object is PHAsset {
let obj:PHAsset = object as! PHAsset
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
let options = PHImageRequestOptions()
options.deliveryMode = .fastFormat
options.isSynchronous = true
imageManager.requestImage(for: obj, targetSize: CGSize(width: obj.pixelWidth, height: obj.pixelHeight), contentMode: .aspectFill, options: options, resultHandler: { img, info in
self.images.append(img!)
})
}
})
When I tried images.count, it said 3. Can anyone help me to find my mistake and get all photos? Big thanks!
Try this,
first import photos
import Photos
then declare Array for store photo before viewDidLoad()
var allPhotos : PHFetchResult<PHAsset>? = nil
Now write code for fetch photo in viewDidLoad()
/// Load Photos
PHPhotoLibrary.requestAuthorization { (status) in
switch status {
case .authorized:
print("Good to proceed")
let fetchOptions = PHFetchOptions()
self.allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
case .denied, .restricted:
print("Not allowed")
case .notDetermined:
print("Not determined yet")
}
}
Now write this code for display image from Array
/// Display Photo
let asset = allPhotos?.object(at: indexPath.row)
self.imageview.fetchImage(asset: asset!, contentMode: .aspectFit, targetSize: self.imageview.frame.size)
// Or Display image in Collection View cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! SelectPhotoCell
let asset = allPhotos?.object(at: indexPath.row)
cell.imgPicture.fetchImage(asset: asset!, contentMode: .aspectFit, targetSize: cell.imgPicture.frame.size)
return cell
}
extension UIImageView{
func fetchImage(asset: PHAsset, contentMode: PHImageContentMode, targetSize: CGSize) {
let options = PHImageRequestOptions()
options.version = .original
PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: options) { image, _ in
guard let image = image else { return }
switch contentMode {
case .aspectFill:
self.contentMode = .scaleAspectFill
case .aspectFit:
self.contentMode = .scaleAspectFit
}
self.image = image
}
}
}
Swift 5 - Update
First import Photos
Import Photos
Create a variable to hold all the images
var images = [UIImage]()
Main function to grab the assets and request image from asset
fileprivate func getPhotos() {
let manager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = false
requestOptions.deliveryMode = .highQualityFormat
// .highQualityFormat will return better quality photos
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let results: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
if results.count > 0 {
for i in 0..<results.count {
let asset = results.object(at: i)
let size = CGSize(width: 700, height: 700)
manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { (image, _) in
if let image = image {
self.images.append(image)
self.collectionView.reloadData()
} else {
print("error asset to image")
}
}
}
} else {
print("no photos to display")
}
}
Check first in info.plist that your app is authorized to access photos from library. than Use the below code to access all photos:
PHPhotoLibrary.requestAuthorization { (status) in
switch status {
case .authorized:
print("You Are Authrized To Access")
let fetchOptions = PHFetchOptions()
let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
print("Found number of: \(allPhotos.count) images")
case .denied, .restricted:
print("Not allowed")
case .notDetermined:
print("Not determined yet")
}
}

How to add pagination to PHAsset fetching from Photos Framework?

I am trying to get all the photos from cameraRoll using Photos framework but its taking a lot of time to fetch all the photos from cameraRoll.
Is their anyway to add pagination to it ?
so i can fetch while scrolling.
var images = [UIImage]()
var assets = [PHAsset]()
fileprivate func assetsFetchOptions() -> PHFetchOptions {
let fetchOptions = PHFetchOptions()
//fetchOptions.fetchLimit = 40 //uncomment to limit photo
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
return fetchOptions
}
fileprivate func fetchPhotos() {
let allPhotos = PHAsset.fetchAssets(with: .image, options: assetsFetchOptions())
DispatchQueue.global(qos: .background).async {
allPhotos.enumerateObjects({ (asset, count, stop) in
//print(count)
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 200, height: 200)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: { (image, info) in
if let image = image {
self.images.append(image)
self.assets.append(asset)
}
if count == allPhotos.count - 1 {
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
})
})
}
}
allPhotos is of type PHFetchResult< PHAsset > which is a lazy collection, ie it doesn't actually go out and get the photo until you ask it for one, which is what .enumerateObjects is doing. You can just grab the photos one at a time with the subscript operator or get a range of objects with objects(at:) to page through the collection as needed.

Swift - How to load videos from photo album and display them in collection view

I used PHImageManager to load images from photo album.
Here is my code...
var photoLibrary = [UIImage]()
func grabPhotos(){
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let fetchResult : PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions) {
if fetchResult.count > 0 {
for i in 0..<fetchResult.count{
imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset , targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: requestOptions, resultHandler: {
image, error in
self.photoLibrary.append(image!)
})
}
}
else{
showAllertToImportImage()//A function to show alert
}
}
}
Now I want to load videos from photo album and I need to show them in UICollectionView.
For loading images in UICollectionView I used UIImageView in the cell. What should I use in the cell to load videos ?
I also need to show the video duration.
Please see answer of fetch video and duration of video
func grabPhotos(){
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let fetchResult : PHFetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions) {
if fetchResult.count > 0 {
for i in 0..<fetchResult.count{
//Used for fetch Image//
imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset , targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: requestOptions, resultHandler: {
image, error in
let imageOfVideo = image! as UIImage
self.photoLibrary.append(imageOfVideo)
})
//Used for fetch Video//
imgManager.requestAVAsset(forVideo: fetchResult.object(at: i) as PHAsset, options: PHVideoRequestOptions(), resultHandler: {(avAsset, audioMix, info) -> Void in
if let asset = avAsset as? AVURLAsset {
//let videoData = NSData(contentsOf: asset.url)
let duration : CMTime = asset.duration
let durationInSecond = CMTimeGetSeconds(duration)
print(durationInSecond)
}
})
}
}
else{
//showAllertToImportImage()//A function to show alert
}
}
}

Resources