Swift - how to get last taken 3 photos from photo library? Swift - how to get last taken 3 photos from photo library? ios ios

Swift - how to get last taken 3 photos from photo library?


Here's a solution using the Photos framework available for devices iOS 8+ :

import Photosclass ViewController: UIViewController {    var images:[UIImage] = []    func fetchPhotos () {        // Sort the images by descending creation date and fetch the first 3        let fetchOptions = PHFetchOptions()        fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]        fetchOptions.fetchLimit = 3        // Fetch the image assets        let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)        // If the fetch result isn't empty,        // proceed with the image request        if fetchResult.count > 0 {            let totalImageCountNeeded = 3 // <-- The number of images to fetch            fetchPhotoAtIndex(0, totalImageCountNeeded, fetchResult)        }    }    // Repeatedly call the following method while incrementing    // the index until all the photos are fetched    func fetchPhotoAtIndex(_ index:Int, _ totalImageCountNeeded: Int, _ fetchResult: PHFetchResult<PHAsset>) {        // Note that if the request is not set to synchronous        // the requestImageForAsset will return both the image        // and thumbnail; by setting synchronous to true it        // will return just the thumbnail        let requestOptions = PHImageRequestOptions()        requestOptions.isSynchronous = true        // Perform the image request        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 you haven't already reached the first            // index of the fetch result and if you haven't            // already stored all of the images you need,            // perform the fetch request again with an            // incremented index            if index + 1 < fetchResult.count && self.images.count < totalImageCountNeeded {                self.fetchPhotoAtIndex(index + 1, totalImageCountNeeded, fetchResult)            } else {                // Else you have completed creating your array                print("Completed array: \(self.images)")            }        })    }}


Details

  • Xcode 10.2 (10E125), Swift 5

Solution features

  • works in asynchronously and thread/queue safety
  • get albums ( + all photos album)
  • optimized for fast scrolling
  • get images with defined size

Info.plist

Add to Info.plist

<key>NSPhotoLibraryUsageDescription</key><string>{bla-bla-bla}</string>

Solution

AtomicArray here: https://stackoverflow.com/a/54565351/4488252

import UIKitimport Photosenum PhotoAlbumViewModel {    case regular(id: Int, title: String, count: Int, image: UIImage, isSelected: Bool)    case allPhotos(id: Int, title: String, count: Int, image: UIImage, isSelected: Bool)    var id: Int { switch self { case .regular(let params), .allPhotos(let params): return params.id } }    var count: Int { switch self { case .regular(let params), .allPhotos(let params): return params.count } }    var title: String { switch self { case .regular(let params), .allPhotos(let params): return params.title } }}class PhotoService {    internal lazy var imageManager = PHCachingImageManager()    private lazy var queue = DispatchQueue(label: "PhotoService_queue",                                           qos: .default, attributes: .concurrent,                                           autoreleaseFrequency: .workItem, target: nil)    private lazy var getImagesQueue = DispatchQueue(label: "PhotoService_getImagesQueue",                                                    qos: .userInteractive, attributes: [],                                                    autoreleaseFrequency: .inherit, target: nil)    private lazy var thumbnailSize = CGSize(width: 200, height: 200)    private lazy var imageAlbumsIds = AtomicArray<Int>()    private let getImageSemaphore = DispatchSemaphore(value: 12)    typealias AlbumData = (fetchResult: PHFetchResult<PHAsset>, assetCollection: PHAssetCollection?)    private let _cachedAlbumsDataSemaphore = DispatchSemaphore(value: 1)    private lazy var _cachedAlbumsData = [Int: AlbumData]()    deinit {        print("____ PhotoServiceImpl deinited")        imageManager.stopCachingImagesForAllAssets()    }}// albumsextension PhotoService {    private func getAlbumData(id: Int, completion: ((AlbumData?) -> Void)?) {        _ = _cachedAlbumsDataSemaphore.wait(timeout: .now() + .seconds(3))        if let cachedAlbum = _cachedAlbumsData[id] {            completion?(cachedAlbum)            _cachedAlbumsDataSemaphore.signal()            return        } else {            _cachedAlbumsDataSemaphore.signal()        }        var result: AlbumData? = nil        switch id {            case 0:                let fetchOptions = PHFetchOptions()                fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]                let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)                result = (allPhotos, nil)            default:                let collections = getAllAlbumsAssetCollections()                let id = id - 1                if  id < collections.count {                    _fetchAssets(in: collections[id]) { fetchResult in                        result = (fetchResult, collections[id])                    }                }        }        guard let _result = result else { completion?(nil); return }        _ = _cachedAlbumsDataSemaphore.wait(timeout: .now() + .seconds(3))        _cachedAlbumsData[id] = _result        _cachedAlbumsDataSemaphore.signal()        completion?(_result)    }    private func getAllAlbumsAssetCollections() -> PHFetchResult<PHAssetCollection> {        let fetchOptions = PHFetchOptions()        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "endDate", ascending: true)]        fetchOptions.predicate = NSPredicate(format: "estimatedAssetCount > 0")        return PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)    }    func getAllAlbums(completion: (([PhotoAlbumViewModel])->Void)?) {        queue.async { [weak self] in            guard let self = self else { return }            var viewModels = AtomicArray<PhotoAlbumViewModel>()            var allPhotosAlbumViewModel: PhotoAlbumViewModel?            let dispatchGroup = DispatchGroup()            dispatchGroup.enter()            self.getAlbumData(id: 0) { data in                   guard let data = data, let asset = data.fetchResult.lastObject else { dispatchGroup.leave(); return }                self._fetchImage(from: asset, userInfo: nil, targetSize: self.thumbnailSize,                                     deliveryMode: .fastFormat, resizeMode: .fast) { [weak self] (image, _) in                                        guard let self = self, let image = image else { dispatchGroup.leave(); return }                                        allPhotosAlbumViewModel = .allPhotos(id: 0, title: "All Photos",                                                                             count: data.fetchResult.count,                                                                             image: image, isSelected: false)                                    self.imageAlbumsIds.append(0)                                    dispatchGroup.leave()                }            }            let numberOfAlbums = self.getAllAlbumsAssetCollections().count + 1            for id in 1 ..< numberOfAlbums {                dispatchGroup.enter()                self.getAlbumData(id: id) { [weak self] data in                    guard let self = self else { return }                    guard let assetCollection = data?.assetCollection else { dispatchGroup.leave(); return }                    self.imageAlbumsIds.append(id)                    self.getAlbumViewModel(id: id, collection: assetCollection) { [weak self] model in                        guard let self = self else { return }                        defer { dispatchGroup.leave() }                        guard let model = model else { return }                        viewModels.append(model)                    }                }            }            _ = dispatchGroup.wait(timeout: .now() + .seconds(3))            var _viewModels = [PhotoAlbumViewModel]()            if let allPhotosAlbumViewModel = allPhotosAlbumViewModel {                _viewModels.append(allPhotosAlbumViewModel)            }            _viewModels += viewModels.get()            DispatchQueue.main.async { completion?(_viewModels) }        }    }    private func getAlbumViewModel(id: Int, collection: PHAssetCollection, completion: ((PhotoAlbumViewModel?) -> Void)?) {        _fetchAssets(in: collection) { [weak self] fetchResult in            guard let self = self, let asset = fetchResult.lastObject else { completion?(nil); return }            self._fetchImage(from: asset, userInfo: nil, targetSize: self.thumbnailSize,                             deliveryMode: .fastFormat, resizeMode: .fast) { (image, nil) in                                guard let image = image else { completion?(nil); return }                                completion?(.regular(id: id,                                                     title: collection.localizedTitle ?? "",                                                     count: collection.estimatedAssetCount,                                                     image: image, isSelected: false))            }        }    }}// fetchextension PhotoService {    fileprivate func _fetchImage(from photoAsset: PHAsset,                                 userInfo: [AnyHashable: Any]? = nil,                                 targetSize: CGSize, //= PHImageManagerMaximumSize,                                 deliveryMode: PHImageRequestOptionsDeliveryMode = .fastFormat,                                 resizeMode: PHImageRequestOptionsResizeMode,                                 completion: ((_ image: UIImage?, _ userInfo: [AnyHashable: Any]?) -> Void)?) {        // guard authorizationStatus() == .authorized else { completion(nil); return }        let options = PHImageRequestOptions()        options.resizeMode = resizeMode        options.isSynchronous = true        options.deliveryMode = deliveryMode        imageManager.requestImage(for: photoAsset,                                  targetSize: targetSize,                                  contentMode: .aspectFill,                                  options: options) { (image, info) -> Void in                                    guard   let info = info,                                            let isImageDegraded = info[PHImageResultIsDegradedKey] as? Int,                                            isImageDegraded == 0 else { completion?(nil, nil); return }                                    completion?(image, userInfo)        }    }    private func _fetchAssets(in collection: PHAssetCollection, completion: @escaping (PHFetchResult<PHAsset>) -> Void) {        let fetchOptions = PHFetchOptions()        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]        let assets = PHAsset.fetchAssets(in: collection, options: fetchOptions)        completion(assets)    }    private func fetchImage(from asset: PHAsset,                            userInfo: [AnyHashable: Any]?,                            targetSize: CGSize,                            deliveryMode: PHImageRequestOptionsDeliveryMode,                            resizeMode: PHImageRequestOptionsResizeMode,                            completion:  ((UIImage?, _ userInfo: [AnyHashable: Any]?) -> Void)?) {        queue.async { [weak self] in            self?._fetchImage(from: asset, userInfo: userInfo, targetSize: targetSize,                              deliveryMode: deliveryMode, resizeMode: resizeMode) { (image, _) in                                DispatchQueue.main.async { completion?(image, userInfo) }            }        }    }    func getImage(albumId: Int, index: Int,                  userInfo: [AnyHashable: Any]?,                  targetSize: CGSize,                  deliveryMode: PHImageRequestOptionsDeliveryMode,                  resizeMode: PHImageRequestOptionsResizeMode,                  completion:  ((_ image: UIImage?, _ userInfo: [AnyHashable: Any]?) -> Void)?) {        getImagesQueue.async { [weak self] in            guard let self = self else { return }            let indexPath = IndexPath(item: index, section: albumId)            self.getAlbumData(id: albumId) { data in                _ = self.getImageSemaphore.wait(timeout: .now() + .seconds(3))                guard let photoAsset = data?.fetchResult.object(at: index) else { self.getImageSemaphore.signal(); return }                self.fetchImage(from: photoAsset,                                userInfo: userInfo,                                targetSize: targetSize,                                deliveryMode: deliveryMode,                                resizeMode: resizeMode) { [weak self] (image, userInfo) in                                    defer { self?.getImageSemaphore.signal() }                                    completion?(image, userInfo)                }            }        }    }}

Usage

private lazy var photoLibrary = PhotoService()private var albums = [PhotoAlbumViewModel]()//....// Get albumsphotoLibrary.getAllAlbums { [weak self] albums in    self?.albums = albums    // reload views}// Get photophotoLibrary.getImage(albumId: albums[0].id,                      index: 1, userInfo: nil,                      targetSize: CGSize(width: 200, height: 200),                      deliveryMode: .fastFormat,                      resizeMode: .fast) { [weak self, weak cell] (image, userInfo) in                        // reload views}

Full Sample (collectionView with images from PhotoLibrary)

ViewController.swift

import UIKitimport Photosclass ViewController: UIViewController {    private weak var collectionView: UICollectionView?    var collectionViewFlowLayout: UICollectionViewFlowLayout? {        return collectionView?.collectionViewLayout as? UICollectionViewFlowLayout    }    private lazy var photoLibrary = PhotoService()    private lazy var numberOfElementsInRow = 4    private lazy var cellIdentifier = "cellIdentifier"    private lazy var supplementaryViewIdentifier = "supplementaryViewIdentifier"    private var albums = [PhotoAlbumViewModel]()    private lazy var cellsTags = [IndexPath: Int]()    private lazy var tagKey = "cellTag"    private lazy var thumbnailImageSize = CGSize(width: 200, height: 200)    override func viewDidLoad() {        let collectionViewFlowLayout = UICollectionViewFlowLayout()        collectionViewFlowLayout.minimumLineSpacing = 5        collectionViewFlowLayout.minimumInteritemSpacing = 5        let _numberOfElementsInRow = CGFloat(numberOfElementsInRow)        let allWidthBetwenCells = _numberOfElementsInRow == 0 ? 0 : collectionViewFlowLayout.minimumInteritemSpacing*(_numberOfElementsInRow-1)        let width = (view.frame.width - allWidthBetwenCells)/_numberOfElementsInRow        collectionViewFlowLayout.itemSize = CGSize(width: width, height: width)        collectionViewFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)        view.addSubview(collectionView)        collectionView.translatesAutoresizingMaskIntoConstraints = false        collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true        collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true        collectionView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true        collectionView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true        collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)        collectionView.register(SupplementaryView.self,                                forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,                                withReuseIdentifier: supplementaryViewIdentifier)        collectionView.backgroundColor = .white        self.collectionView = collectionView        collectionView.delegate = self        showAllPhotosButtonTouchedInside()        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "All", style: .done, target: self,                                                           action: #selector(showAllPhotosButtonTouchedInside))        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "last 3", style: .done, target: self,                                                            action: #selector(showLastSeveralPhotosButtonTouchedInside))    }    @objc func showAllPhotosButtonTouchedInside() {    photoLibrary.getAllAlbums { [weak self] albums in        self?.set(albums: albums)        if self?.collectionView?.dataSource == nil {            self?.collectionView?.dataSource = self        } else {            self?.collectionView?.reloadData()        }    }    }    @objc func showLastSeveralPhotosButtonTouchedInside() {        photoLibrary.getAllAlbums { [weak self] albums in            guard let firstAlbum = albums.first else { return }            var album: PhotoAlbumViewModel!            let maxPhotosToDisplay = 3            switch firstAlbum {                case .allPhotos(let id, let title, let count, let image, let isSelected):                    let newCount = count > maxPhotosToDisplay ? maxPhotosToDisplay : count                    album = .allPhotos(id: id, title: title, count: newCount, image: image, isSelected: isSelected)                case .regular(let id, let title, let count, let image, let isSelected):                    let newCount = count > maxPhotosToDisplay ? maxPhotosToDisplay : count                    album = .regular(id: id, title: title, count: newCount, image: image, isSelected: isSelected)            }            self?.set(albums: [album])            if self?.collectionView?.dataSource == nil {                self?.collectionView?.dataSource = self            } else {                self?.collectionView?.reloadData()            }        }    }    private func set(albums: [PhotoAlbumViewModel]) {        self.albums = albums        var counter = 0        for (section, album) in albums.enumerated() {            for row in 0..<album.count {                self.cellsTags[IndexPath(row: row, section: section)] = counter                counter += 1            }        }    }}extension ViewController: UICollectionViewDataSource {    func numberOfSections(in collectionView: UICollectionView) -> Int { return albums.count }    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {        return albums[section].count    }    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {        let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: supplementaryViewIdentifier, for: indexPath) as! SupplementaryView        header.label?.text = albums[indexPath.section].title        return header    }    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CollectionViewCell        let tag = cellsTags[indexPath]!        cell.tag = tag        photoLibrary.getImage(albumId: albums[indexPath.section].id,                              index: indexPath.item, userInfo: [tagKey: tag],                              targetSize: thumbnailImageSize,                              deliveryMode: .fastFormat,                              resizeMode: .fast) { [weak self, weak cell] (image, userInfo) in                                guard   let cell = cell, let tagKey = self?.tagKey,                                        let cellTag = userInfo?[tagKey] as? Int,                                        cellTag == cell.tag else { return }                                cell.imageView?.image = image        }        return cell    }}extension ViewController: UICollectionViewDelegate {}

CollectionViewCell.swift

import UIKitclass CollectionViewCell: UICollectionViewCell {    weak var imageView: UIImageView?    override init(frame: CGRect) {        super.init(frame: frame)        clipsToBounds = true        let imageView = UIImageView(frame: .zero)        imageView.contentMode = .scaleAspectFill        addSubview(imageView)        imageView.translatesAutoresizingMaskIntoConstraints = false        imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true        imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true        imageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true        imageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true        self.imageView = imageView        backgroundColor = UIColor.lightGray.withAlphaComponent(0.3)    }    required init?(coder aDecoder: NSCoder) {        super.init(coder: aDecoder)    }    override func prepareForReuse() {        super.prepareForReuse()        imageView?.image = nil    }}

SupplementaryView.swift

import UIKitclass SupplementaryView: UICollectionReusableView {    weak var label: UILabel?    override init(frame: CGRect) {        super.init(frame: frame)        backgroundColor = .white        let label = UILabel(frame: frame)        label.textColor = .black        addSubview(label)        self.label = label    }    required init?(coder aDecoder: NSCoder) {        super.init(coder: aDecoder)    }    override func prepareForReuse() {        super.prepareForReuse()        self.label?.text = nil    }}

Storyboard

enter image description here

Results

enter image description hereenter image description here


Here's an elegant solution with efficiency in Swift 4.

In short, we request the latest photo assets once, then convert them into image when needed.

First import Photos Library:

import Photos

Then create a function to fetch the lastest photos taken:

func fetchLatestPhotos(forCount count: Int?) -> PHFetchResult<PHAsset> {    // Create fetch options.    let options = PHFetchOptions()    // If count limit is specified.    if let count = count { options.fetchLimit = count }    // Add sortDescriptor so the lastest photos will be returned.    let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)    options.sortDescriptors = [sortDescriptor]    // Fetch the photos.    return PHAsset.fetchAssets(with: .image, options: options)}

In your case you might want to fetch enough photos at once (for example 50), then store the result somewhere in your view controller:

var latestPhotoAssetsFetched: PHFetchResult<PHAsset>? = nil

In viewDidLoad:

self.latestPhotoAssetsFetched = self.fetchLatestPhotos(forCount: 50)

Finally request the image at the right place (for example, a collection view cell):

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {    /*     ...your code to configure the cell...     */    // Get the asset. If nothing, return the cell.    guard let asset = self.latestPhotoAssetsFetched?[indexPath.item] else {        return cell    }    // Here we bind the asset with the cell.    cell.representedAssetIdentifier = asset.localIdentifier    // Request the image.    PHImageManager.default().requestImage(for: asset,                                   targetSize: cell.imageView.frame.size,                                  contentMode: .aspectFill,                                      options: nil) { (image, _) in        // By the time the image is returned, the cell may has been recycled.        // We update the UI only when it is still on the screen.        if cell.representedAssetIdentifier == asset.localIdentifier {            cell.imageView.image = image        }    }    return cell}

Remember to add a property to your cell:

class PhotoCell: UICollectionViewCell {    var representedAssetIdentifier: String? = nil}