Swift - how to get last taken 3 photos from photo

2019-01-16 13:20发布

问题:

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.

回答1:

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)")
            }
        })
    }
}


回答2:

Details

xCode 8.3, Swift 3.1

Code

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
    }
}

Usage

 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
              }
      }
 }

Full Sample (collectionView with images from PhotoLibrary)

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>

Result



回答3:

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.
})


回答4:

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
}


回答5:

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];

}