Swift - 如何从照片库中获取最近拍摄的 3 张照片?

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

我需要在 viewDidload 活动中获取并显示照片库中最后拍摄的 3 张照片,无需任何点击。

完成这一步后,当我滚动 ScrollView 时,我应该会得到其他照片 3 x 3。

您知道使用 swift 执行此操作的正确方法吗?谢谢

您可以使用AssetsLibrary框架中的函数提取最新的3张照片。首先,您必须将框架添加到项目中。以下函数检索 3 张最新照片并调用完成块。

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

上面的函数可以这样调用

getLatestPhotos(completion: { images in
     println(images)
     //Set Images in this block.
})

这是一个使用 Photos 框架的解决方案,适用于 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)")
            }
        })
    }
}

这是@Lindsey Scott 的回答,但在 Objective-C 中。我将相机胶卷中的最后 9 张照片放入 collection 视图:

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

}

详情

  • Xcode 10.2 (10E125), Swift 5

解决方案特点

  • 异步工作并且thread/queue安全
  • 获取相册(+所有照片相册)
  • 针对快速滚动进行了优化
  • 获取定义大小的图像

Info.plist

Add to Info.plist

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

解决方案

AtomicArray here:

import UIKit
import Photos

enum 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()
    }
}

// albums

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

// fetch

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

用法

private lazy var photoLibrary = PhotoService()
private var albums = [PhotoAlbumViewModel]()
//....

// Get albums
photoLibrary.getAllAlbums { [weak self] albums in
    self?.albums = albums
    // reload views
}

// Get photo
photoLibrary.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
}

完整示例(带有来自 PhotoLibrary 的图像的 collectionView)

ViewController.swift

import UIKit
import Photos

class 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 UIKit

class 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 UIKit

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

故事板

结果

这是一个优雅的解决方案,效率在 Swift 4.

简而言之,我们请求一次最新的照片资源,然后在需要时将其转换为图像。

首先导入照片库:

import Photos

然后创建一个函数来获取最新拍摄的照片:

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)

}

在您的情况下,您可能希望一次获取足够多的照片(例如 50 张),然后将结果存储在视图控制器中的某个位置:

var latestPhotoAssetsFetched: PHFetchResult<PHAsset>? = nil

viewDidLoad中:

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

最终在正确的位置请求图像(例如,集合视图单元格):

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
}

记得在您的单元格中添加一个 属性:

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