iOS照片框架-PhotoKit

1.介绍

PhotoKitApp在使用、管理图片和视频的框架,并且还包括了iCloud上面的图片以及实时照片.html

2.概要

  • iOS中,PhotoKit支持应用构建照片以及编辑扩展,还能够直接访问管理照片和视频元资源以及元资源集合例如专辑,时刻和共享相册.

3.官方Demo-PhotoBrowse

Browsing and Modifying Photo Albumsapi

此示例演示如何使用自定义实现相似的布局.它使用PhotoKit获取资源缩略图,而后将其显示为单个照片,视频或动态图片。此外示例应用程序PhotoBrowse还演示了如何将用户的照片整理到相册和内置集合中,例如最近添加的和收藏夹.它支持专辑的建立,删除,修改,以及我的资源的编辑和收藏.数组

3.1 获取全部相册,全部照片请求

let allPhotosOptions = PHFetchOptions()
    allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
    // 获取全部照片
    allPhotos = PHAsset.fetchAssets(with: allPhotosOptions)
    // 获取智能相册
    smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options:     nil)
    // 获取用户建立的全部相册
    userCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil)
复制代码

获取操做是由上面描述的实体的类方法实现的.要使用哪一个类/方法,取决于问题所在范围和你展现与遍历照片库的方式.全部获取方法的命名都是类似的:class func fetchXXX(..., options: PHFetchOptions) -> PHFetchResult .options参数给了咱们一个对结果进行过滤和排序的途径,这和 NSFetchRequestpredicatesortDescriptors参数相似.缓存

3.2 观察变化

首先,你须要经过共享的PHPhotoLibrary对象,用registerChangeObserver(...)方法注册一个变化观察者 (这个观察者要听从PHPhotoLibraryChangeObserver协议).只要另外一个应用或者用户在照片库中作的修改影响了你在变化前获取的任何资源或资源集合的话,变化观察者的photoLibraryDidChange(...)方法都会被调用.这个方法只有一个 PHChange类型的参数,你能够用它来验证这些变化是否和你所感兴趣的获取对象有关联.安全

更新获取的结果PHChange提供了几个方法,让你能够经过传入任何你感兴趣的PHObject对象或PHFetchResult 对象来追踪它们的变化.这几个方法是changeDetailsForObject(...)changeDetailsForFetchResult(...) .若是没有任何变化,这些方法会返回nil,不然你能够借助PHObjectChangeDetailsPHFetchResultChangeDetails 对象来观察这些变化。markdown

PHObjectChangeDetails提供了一个对最新的照片实体对象的引用,以及告诉你对象的图像数据是否曾变化过、对象是否曾被删除过的布尔值.PHFetchResultChangeDetails封装了施加在你以前经过获取所获得的PHFetchResult 上的变化的信息.PHFetchResultChangeDetails是为了尽量简化CollectionViewTableView 的更新操做而设计的.它的属性刚好映射到你在使用一个典型的CollectionViewupdate handler 时所须要的信息.注意,若要正确的更新UITableView/UICollectionView,你必须以正确顺序来处理变化,那就是:RICE —— removedIndexes,insertedIndexes,changedIndexes,enumerateMovesWithBlock (若是hasMovestrue 的话).另外,PHFetchResultChangeDetailshasIncrementalChanges属性能够被设置成 false,这意味着旧的获取结果应该所有被新的值代替.这种状况下,你应该调用1UITableView/UICollectionView1的 reloadData.网络

注意:没有必要以集中的方式处理变化.若是你应用中的多个组件须要处理照片实体,那么它们每一个都要有本身的 PHPhotoLibraryChangeObserver.接着组件就能靠本身查询PHChange对象,检测是否须要 (以及如何)更新它们本身的状态。架构

// 注册监听,获取相册数据源变化,系统提供回调方法-photoLibraryDidChange
    PHPhotoLibrary.shared().register(self)

    func photoLibraryDidChange(_ changeInstance: PHChange) {
        // 接收通知可能会在后台线程,因此此处的UI更新需放于主线程调用
        DispatchQueue.main.sync {
            // Check each of the three top-level fetches for changes.
            if let changeDetails = changeInstance.changeDetails(for: allPhotos) {
                // 更新缓存的数据源
                allPhotos = changeDetails.fetchResultAfterChanges
            }
            
            // 更新缓存的数据源并更新UI
            if let changeDetails = changeInstance.changeDetails(for: smartAlbums) {
                smartAlbums = changeDetails.fetchResultAfterChanges
                tableView.reloadSections(IndexSet(integer: Section.smartAlbums.rawValue), with: .automatic)
            }
            if let changeDetails = changeInstance.changeDetails(for: userCollections) {
                userCollections = changeDetails.fetchResultAfterChanges
                tableView.reloadSections(IndexSet(integer: Section.userCollections.rawValue), with: .automatic)
            }
        }
    }
    
    // 取消监听
    PHPhotoLibrary.shared().unregisterChangeObserver(self)
复制代码

3.3 缓存以及展现列表缩略图

当图像即将要展现在屏幕上时,好比当要在一组滚动的collection视图上展现大量的资源图像的缩略图时,预先将一些图像加载到内存中有时是很是有用的.PhotoKit提供了一个PHImageManager的子类来处理这种特定的使用场景 —— PHImageCachingManager.app

PHImageCachingManager提供了一个关键方法startCachingImagesForAssets(...)你传入一个PHAssets类型的数组,一些请求参数,以及一些请求单个图像时即将用到的可选项.此外,还有一些方法可让你通知缓存管理器来中止缓存特定资源列表,以及中止缓存全部图像.框架

默认状况下,若是图像管理器决定要用最优策略,那么它会在将图像的高质量版本递送给你以前,先传递一个较低质量的版本.你能够经过deliveryMode属性来控制这个行为;上面所描述的默认行为的值为 .Opportunistic.若是你只想要高质量的图像,而且能够接受更长的加载时间,那么将属性设置为 .HighQualityFormat.若是你想要更快的加载速度,且能够牺牲一点图像质量,那么将属性设置为.FastFormat.

你可使用PHImageRequestOptionssynchronous属性,让requestImage...系列的方法变成同步操做。注意:当 synchronous设为true时,deliveryMode属性就会被忽略,并被看成.HighQualityFormat来处理.在设置这些参数时,必定要考虑到你的一些用户有可能开启了iCloud照片库,这点很是重要.PhotoKitAPI不必定会对设备的照片和 iCloud上照片进行区分 —— 它们都用同一个requestImage方法来加载.这意味着任意一个图像请求都有多是一个经过蜂窝网络来进行的很是缓慢的网络请求.当你要用.HighQualityFormat或者作一个同步请求的时候,要牢记这个.注意:若是你想要确保请求不通过网络,能够将 networkAccessAllowed设为false.另外一个和iCloud相关的属性是 progressHandler.你能够将它设为一个PHAssetImageProgressHandlerblock,当从iCloud下载照片时,它就会被图像管理器自动调用。

这里等页面展现时预先缓存数据后再取数据后展现.

let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect)
let addedAssets = addedRects
    .flatMap { rect in collectionView!.indexPathsForElements(in: rect) }
    .map { indexPath in fetchResult.object(at: indexPath.item) }
let removedAssets = removedRects
    .flatMap { rect in collectionView!.indexPathsForElements(in: rect) }
    .map { indexPath in fetchResult.object(at: indexPath.item) }

// 更新缓存数据
imageManager.startCachingImages(for: addedAssets,
                                targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)
imageManager.stopCachingImages(for: removedAssets,
                               targetSize: thumbnailSize, contentMode: .aspectFill, options: nil)
复制代码
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GridViewCell", for: indexPath) as? GridViewCell
    else { fatalError("Unexpected cell in collection view") }

// 给动态照片Cell添加一个badge
if asset.mediaSubtypes.contains(.photoLive) {
    cell.livePhotoBadgeImage = PHLivePhotoView.livePhotoBadgeImage(options: .overContent)
}

// 经过PHCachingImageManager请求照片
cell.representedAssetIdentifier = asset.localIdentifier
imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil, resultHandler: { image, _ in
    // UIKit may have recycled this cell by the handler's activation time.
    // 只有请求到通用类型标识符一致的资源时才配置图片
    if cell.representedAssetIdentifier == asset.localIdentifier {
        cell.thumbnailImage = image
    }
})
复制代码

3.4 编辑修改资源文件

PhotoKit在照片库作改变,说到底实际上是先建立了一个连接到某个资源或者资源集合的变化请求对象,再设置请求对象的相关属性或调用合适的方法来描述你想要提交的变化.这个必须经过performChanges(...)方法,在提交到共享的PHPhotoLibraryblock内完成.注意:你须要准备好在performChanges方法的completion block 里处理失败的状况.虽然处理的是能被多个参与者(如你的应用,用户,其余应用,照片扩展等)改变的状态,但这个方式能提供安全性,也相对易用.

想要修改资源,须要建立一个PHAssetChangeRequest,而后你就能够修改建立建立日期,资源位置,以及是否将隐藏资源,是否将资源看作用户收藏等.此外,你还能够从用户的库里删除资源.相似地,若要修改资源集合或集合列表,须要建立一个 PHAssetCollectionChangeRequestPHCollectionListChangeRequest对象.而后你就能够修改集合标题,添加或删除集合成员,或者彻底删除集合.

在你的变化提交到用户照片库前,系统会向用户展现一个明确的获取权限的警告框.

3.4.1 修改资源

DispatchQueue.global(qos: .userInitiated).async {
    // 建立调整的数据源.
    // 若是你对一张照片进行了滤镜处理,你应该会建立一个adjustmentData对象,它记录了用户选择了什么滤镜、相关配置的参数、以及使用这(款/些)滤镜的命令.接下去,用户能够用你的app或者别的能够获取你的adjustmentData对象的app来恢复这张照片到初始状态
    let adjustmentData = PHAdjustmentData(formatIdentifier: self.formatIdentifier,
                                          formatVersion: self.formatVersion,
                                          data: filterName.data(using: .utf8)!)
    
    // 建立输出数据源.
    let output = PHContentEditingOutput(contentEditingInput: input)
    output.adjustmentData = adjustmentData
    
    // 设置滤镜类型.
    let applyFunc: (String, PHContentEditingInput, PHContentEditingOutput, @escaping () -> Void) -> Void
    if self.asset.mediaSubtypes.contains(.photoLive) {
        applyFunc = self.applyLivePhotoFilter
    } else if self.asset.mediaType == .image {
        applyFunc = self.applyPhotoFilter
    } else {
        applyFunc = self.applyVideoFilter
    }
    
    // 使用滤镜.
    applyFunc(filterName, input, output, {
        // 当滤镜提交使用后,再更新到资源库.
        PHPhotoLibrary.shared().performChanges({
            let request = PHAssetChangeRequest(for: self.asset)
            request.contentEditingOutput = output
        }, completionHandler: { success, error in
            if !success { print("Can't edit the asset: \(String(describing: error))") }
        })
    })
}
复制代码

3.4.2 修改资源集合或集合列表

PHPhotoLibrary.shared().performChanges({
    PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: title)
}, completionHandler: { success, error in
    if !success { print("Error creating album: \(String(describing: error)).") }
})
复制代码

3.4.3 添加新资源

PHPhotoLibrary.shared().performChanges({
    let creationRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
    if let assetCollection = self.assetCollection {
        let addAssetRequest = PHAssetCollectionChangeRequest(for: assetCollection)
        addAssetRequest?.addAssets([creationRequest.placeholderForCreatedAsset!] as NSArray)
    }
}, completionHandler: {success, error in
    if !success { print("Error creating the asset: \(String(describing: error))") }
})
复制代码

3.4.4 删除资源

if assetCollection != nil {
    // 从当前选中的相册|集合中删除资源
    PHPhotoLibrary.shared().performChanges({
        let request = PHAssetCollectionChangeRequest(for: self.assetCollection)!
        request.removeAssets([self.asset] as NSArray)
    }, completionHandler: completion)
} else {
    // 直接从资源库中删除.
    PHPhotoLibrary.shared().performChanges({
        PHAssetChangeRequest.deleteAssets([self.asset] as NSArray)
    }, completionHandler: completion)
}
复制代码

3.5 其余

判断是不是Favorite的相册,以及添加Favorite

PHPhotoLibrary.shared().performChanges({
    let request = PHAssetChangeRequest(for: self.asset)
    request.isFavorite = !self.asset.isFavorite
}, completionHandler: { success, error in
    if success {
        DispatchQueue.main.sync {
            sender.title = self.asset.isFavorite ? "♥︎" : "♡"
        }
    } else {
        print("Can't mark the asset as a Favorite: \(String(describing: error))")
    }
})
复制代码

4.API介绍

4.1 PhotoKit框架经常使用类简介图

4.2 PhotoKit框架构成图

4.3 经常使用类

原文: iOS照片框架

4.3.1 基本

PHPhotoLibrary: 表示用户的照片库,用于请求、获取照片库的权限,监听照片库的变化;`

4.3.2 资源

PHObject: 抽象基类,其余PhotoKit类都继承该类,提供了一个localIdentifier属性;

PHAsset: 表示照片库中的一个单独的资源(能够是图片,也能够是视频;与ALAsset相似),用于获取、保存资源的元数据;PHAsset只包含元数据(如图片大小、建立日期等),具体的图片、视频数据须要使用PHImageManager进行加载;

若一个资源的representsBurst属性为true,则表示该资源是一系列连拍照片中的表明照片,能够经过fetchAssetsWithBurstIdentifier()方法,传入burstIdentifier属性,获取连拍照片中的剩余的其余照片;

PHCollection: PHAssetCollection和PHCollectionList的父类;

PHAssetCollection: 资源集合,表示成组的资源;能够表示照片库中的一个相册、时刻、智能相册;

智能相册:系统默认提供的特定相册,如最近删除、视频列表、收藏等;

PHCollectionList: 表示一组PHCollections的集合;其自己也是PHCollection,故PHCollectionList也能够包含其余的PHCollectionList;

4.3.3 获取

PHFetchResult: 表示一系列的PHAsset的结果集合,也能够是一系列的PHCollection的结果集合;

PHFetchOptions: 获取PHFetchResult时的传入参数,起过滤、排序等做用,能够过滤类型、日期、名称等;

传入nil则使用系统默认值;

PHImageManager: 用于加载资源;

PHCachingImageManager: 继承于PHImageManager,带有缓存的加载资源;当使用大量的资源时,能够先在后台准备资源图片,减小在以后的请求单个资源时的延迟;如当想要使用照片、视频资源的缩略图填充一个集合视图时就可使用PHCachingImageManager;先使用startCachingImagesForAssets方法进行资源准备,以后还使用PHImageManager的request方法加载资源;

PHImageRequestOptions: 加载资源时的传入参数,控制资源的输出尺寸等规格;

4.3.4 更新

PHAssetChangeRequest: 用来建立、删除和修改PHAsset对象;

PHAssetCollectionChangeRequest: 用来建立、删除和修改PHAssetCollection对象;

PHCollectionListChangeRequest: 用来建立、删除和修改PHCollectionList对象;

PHAssetCreationRequest: PHAssetChangeRequest的子类,也能够用来建立,丰富了添加资源的方式;

4.3.5 相关枚举值

注意:获取指定类型的相册时,主类型和子类型要匹配,若不匹配则系统会按照any子类型处理; 对于moment类型,子类型使用any; 对于smartAlbum类型,子类型使用albumRegular比使用any多一个Recently Deleted(最近删除)的相册;

enum PHAssetCollectionType : Int {
    case album // 用户本身在Photos app中创建的相册、从iTunes同步来的相册
    case smartAlbum // Photos app内置的相册(内容动态更新)
    case moment // Photos app自动生成的时间、地点分组的相册
}
复制代码
enum PHAssetCollectionSubtype : Int {
    // PHAssetCollectionTypeAlbum regular subtypes
    case albumRegular // 用户本身在Photos app中创建的相册
    case albumSyncedEvent // 已废弃;从iPhoto同步来的事件相册
    case albumSyncedFaces // 从iPhoto同步来的人物相册
    case albumSyncedAlbum // 从iPhoto同步来的相册
    case albumImported // 从相机或外部存储导入的相册
    // PHAssetCollectionTypeAlbum shared subtypes
    case albumMyPhotoStream // 用户的iCloud照片流
    case albumCloudShared // //用户使用iCloud共享的相册

    // PHAssetCollectionTypeSmartAlbum subtypes
    case smartAlbumGeneric // 非特殊类型的相册,从macOS Photos app同步过来的相册
    case smartAlbumPanoramas // 相机拍摄的全景照片的相册
    case smartAlbumVideos // 相机拍摄的视频的相册
    case smartAlbumFavorites // 收藏的照片、视频的相册
    case smartAlbumTimelapses // 延时视频的相册
    case smartAlbumAllHidden // 包含隐藏照片、视频的相册
    case smartAlbumRecentlyAdded // 相机近期拍摄的照片、视频的相册
    case smartAlbumBursts // 连拍模式拍摄的照片的相册
    case smartAlbumSlomoVideos // Slomo是slow motion的缩写,高速摄影慢动做解析(iOS设备以120帧拍摄)的相册
    case smartAlbumUserLibrary // 相机相册,包含相机拍摄的全部照片、视频,使用其余应用保存的照片、视频
    @available(iOS 9.0, *)
    case smartAlbumSelfPortraits
    @available(iOS 9.0, *)
    case smartAlbumScreenshots
    @available(iOS 10.2, *)
    case smartAlbumDepthEffect
    @available(iOS 10.3, *)
    case smartAlbumLivePhotos
    @available(iOS 11.0, *)
    case smartAlbumAnimated
    @available(iOS 11.0, *)
    case smartAlbumLongExposures
    // Used for fetching, if you don't care about the exact subtype
    case any // 包含全部类型
}
复制代码

5.总结

iOS 8以前,开发者只能使用AssetsLibrary框架来处理照片,但随着苹果手机的不断更新,设备的不断进步,照片功能不断更新,原有的AssetsLibrary框架已经再也不能跟得上脚步了.但随着 iOS 8 的到到来,苹果给咱们提供了一个现代化的框架PhotoKit,使得开发者们可以更好的适应潮流,更好的开发相关应用,让应用与设备可以完美无缝的工做.

参考

Photos框架获取本地图片视频信息

照片框架

苹果官方文档中文译本之-PHAdjustmentData