I need to get and show last taken 3 photos from photo library on viewDidload event without any clicks.
After this step I should get other photos 3 by 3 when I scroll the scrollview.
Do you know the proper way to do this with swift? Thanks.
I need to get and show last taken 3 photos from photo library on viewDidload event without any clicks.
After this step I should get other photos 3 by 3 when I scroll the scrollview.
Do you know the proper way to do this with swift? Thanks.
Here's a solution using the Photos
framework available for devices iOS 8+ :
import Photos
class 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)")
}
})
}
}
xCode 8.3, Swift 3.1
import UIKit
import Photos
class PhotoLibrary {
fileprivate var imgManager: PHImageManager
fileprivate var requestOptions: PHImageRequestOptions
fileprivate var fetchOptions: PHFetchOptions
fileprivate var fetchResult: PHFetchResult<PHAsset>
init () {
imgManager = PHImageManager.default()
requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: true)]
fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
}
var count: Int {
return fetchResult.count
}
func setPhoto(at index: Int, completion block: @escaping (UIImage?)->()) {
if index < fetchResult.count {
imgManager.requestImage(for: fetchResult.object(at: index) as PHAsset, targetSize: UIScreen.main.bounds.size, contentMode: PHImageContentMode.aspectFill, options: requestOptions) { (image, _) in
block(image)
}
} else {
block(nil)
}
}
func getAllPhotos() -> [UIImage] {
var resultArray = [UIImage]()
for index in 0..<fetchResult.count {
imgManager.requestImage(for: fetchResult.object(at: index) as PHAsset, targetSize: UIScreen.main.bounds.size, contentMode: PHImageContentMode.aspectFill, options: requestOptions) { (image, _) in
if let image = image {
resultArray.append(image)
}
}
}
return resultArray
}
}
var photoLibrary = PhotoLibrary()
// Number of all photos
photoLibrary.count
// Get photo
self.photoLibrary.setPhoto(at: indexPath.row) { image in
if let image = image {
DispatchQueue.main.async {
imageView.image = image
}
}
}
Info.plist
Add to Info.plist
<key>NSPhotoLibraryUsageDescription</key>
<string>{bla-bla-bla}</string>
ViewController.swift
import UIKit
import Photos
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var collectionViewFlowLayout: UICollectionViewFlowLayout!
fileprivate var photoLibrary: PhotoLibrary!
fileprivate var numberOfSections = 0
override func viewDidLoad() {
initCollectionView()
PHPhotoLibrary.requestAuthorization { [weak self] result in
if let _self = self {
if result == .authorized {
_self.photoLibrary = PhotoLibrary()
_self.numberOfSections = 1
DispatchQueue.main.async {
_self.collectionView.reloadData()
}
}
}
}
}
}
extension ViewController: UICollectionViewDataSource {
fileprivate var numberOfElementsInRow: Int {
return 4
}
var sizeForCell: CGSize {
let _numberOfElementsInRow = CGFloat(numberOfElementsInRow)
let allWidthBetwenCells = _numberOfElementsInRow == 0 ? 0 : collectionViewFlowLayout.minimumInteritemSpacing*(_numberOfElementsInRow-1)
let width = (collectionView.frame.width - allWidthBetwenCells)/_numberOfElementsInRow
return CGSize(width: width, height: width)
}
func initCollectionView() {
collectionView.dataSource = self
collectionView.delegate = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return numberOfSections
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoLibrary.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return sizeForCell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let cell = cell as! CollectionViewCell
cell.cellImageView.image = nil
DispatchQueue.global(qos: .background).async {
self.photoLibrary.setPhoto(at: indexPath.row) { image in
if let image = image {
DispatchQueue.main.async {
cell.cellImageView.image = image
}
}
}
}
}
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
@IBOutlet weak var cellImageView: UIImageView!
}
PhotoLibrary.swift
Described above
Main.storyboard
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_28259961" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="IZe-8T-NdF">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="2" minimumInteritemSpacing="2" id="DNv-oA-G6j">
<size key="itemSize" width="100" height="100"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="CollectionViewCell" id="FND-c4-nYC" customClass="CollectionViewCell" customModule="stackoverflow_28259961" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="FA0-5K-Pxh">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
</imageView>
</subviews>
</view>
<color key="backgroundColor" red="0.82995896879999997" green="0.82995896879999997" blue="0.82995896879999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="FA0-5K-Pxh" firstAttribute="top" secondItem="FND-c4-nYC" secondAttribute="top" id="0WE-w2-xTC"/>
<constraint firstAttribute="trailing" secondItem="FA0-5K-Pxh" secondAttribute="trailing" id="7rj-8k-UrU"/>
<constraint firstItem="FA0-5K-Pxh" firstAttribute="leading" secondItem="FND-c4-nYC" secondAttribute="leading" id="ofw-aq-B79"/>
<constraint firstAttribute="bottom" secondItem="FA0-5K-Pxh" secondAttribute="bottom" id="ogx-56-qNt"/>
</constraints>
<connections>
<outlet property="cellImageView" destination="FA0-5K-Pxh" id="DE1-DT-Oik"/>
</connections>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="IZe-8T-NdF" secondAttribute="trailing" id="1W1-Fl-ZL8"/>
<constraint firstItem="IZe-8T-NdF" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="9QC-93-vwd"/>
<constraint firstItem="IZe-8T-NdF" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="BEF-2W-Otd"/>
<constraint firstItem="IZe-8T-NdF" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="isg-PJ-70G"/>
</constraints>
</view>
<connections>
<outlet property="collectionView" destination="IZe-8T-NdF" id="z6G-PD-d44"/>
<outlet property="collectionViewFlowLayout" destination="DNv-oA-G6j" id="y8U-CI-3CD"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="133.59999999999999" y="129.98500749625188"/>
</scene>
</scenes>
</document>
You can extract the 3 latest photos using functions in the AssetsLibrary
framework. First you have to add the framework to the project. The following function retrieves the 3 latest photos and calls the completion block.
import AssetsLibrary
func getLatestPhotos(completion completionBlock : ([UIImage] -> ())) {
let library = ALAssetsLibrary()
var count = 0
var images : [UIImage] = []
var stopped = false
library.enumerateGroupsWithTypes(ALAssetsGroupSavedPhotos, usingBlock: { (group,var stop) -> Void in
group?.setAssetsFilter(ALAssetsFilter.allPhotos())
group?.enumerateAssetsWithOptions(NSEnumerationOptions.Reverse, usingBlock: {
(asset : ALAsset!, index, var stopEnumeration) -> Void in
if (!stopped)
{
if count >= 3
{
stopEnumeration.memory = ObjCBool(true)
stop.memory = ObjCBool(true)
completionBlock(images)
stopped = true
}
else
{
// For just the thumbnails use the following line.
let cgImage = asset.thumbnail().takeUnretainedValue()
// Use the following line for the full image.
let cgImage = asset.defaultRepresentation().fullScreenImage().takeUnretainedValue()
if let image = UIImage(CGImage: cgImage) {
images.append(image)
count += 1
}
}
}
})
},failureBlock : { error in
println(error)
})
}
The above function can be called like this
getLatestPhotos(completion: { images in
println(images)
//Set Images in this block.
})
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
}
Here is @Lindsey Scott's answer but in Objective-C. I am putting the last 9 photos from Camera Roll into a collection view:
-(void)fetchPhotoFromEndAtIndex:(int)index{
PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init];
options.synchronous = YES;
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc]init];
fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *photos = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
if (photos) {
[[PHImageManager defaultManager] requestImageForAsset:[photos objectAtIndex:photos.count -1 -index] targetSize:CGSizeMake(self.collectionView.frame.size.width/3, self.collectionView.frame.size.height/3) contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage *result, NSDictionary *info) {
[self.imagesArray addObject:result];
if (index + 1 < photos.count && self.imagesArray.count < 9) {
[self fetchPhotoFromEndAtIndex:index + 1];
}
}];
}
[self.collectionView reloadData];
}