iOS PhotoKit 笔记

最近有自定义相册需求,就学习一了下 PhotoKit 简单记录一下笔记。

PhotoKit 是 iOS 8 引入的相比 AssetsLibrary 更完整也更高效的库,对资源的处理跟 AssetsLibrary 也有很大的不同。可以从官方 Session Introducing the Photos Frameworks 开始了解 PhotoKit。

此文提到的点的具体的实现可以看我的开源库: BMAssetPicker

基本概念

PhotoKit 所有PhotoKit对象都继承PHObject抽象类。

  • PHAsset: 代表照片库中的一个资源,跟 ALAsset 类似,通过 PHAsset 可以获取和保存资源
  • PHFetchOptions: 获取资源时的参数,可以传 nil,即使用系统默认值
  • PHAssetCollection: PHCollection 的子类,表示一个相册或者一个时刻,或者是一个「智能相册(系统提供的特定的一系列相册,例如:最近删除,视频列表,收藏等等)
  • PHFetchResult: 表示一系列的资源结果集合,也可以是相册的集合,从 PHCollection 的类方法中获得
  • PHImageManager: 用于处理资源的加载,加载图片的过程带有缓存处理,可以通过传入一个 PHImageRequestOptions 控制资源的输出尺寸等规格
  • PHImageRequestOptions: 如上面所说,控制加载图片时的一系列参数
  • PHCollectionList: 表示一组PHCollections。在照片应用可以看到它,照片 - 时刻 - 精选 - 年度。

ios8-photo-kit

获取资源

从 PhotoKit 直接获取的最小资源单位为 PHAsset,是一个只读枚举类型。包含照片尺寸,日期,类型等。从 PHAsset 获取图像或视频需要依赖 PHImageManager。

做一个图片选择的思路:获取权限 —> 列出所有相册 —> 列出相册中的图片

获取权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func authorize(status: PHAuthorizationStatus = PHPhotoLibrary.authorizationStatus(), completion: (authorized: Bool) -> Void) {
switch status {
case .Authorized:
// 授权成功
completion(authorized: true)
case .NotDetermined:
// 没有申请过权限,开始申请权限
PHPhotoLibrary.requestAuthorization({ (status) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.authorize(status, completion: completion)
})
})
default: ()
// 访问相册被拒绝,提醒用户去设置授权
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(authorized: false)
})
}
}

获取相册

获取权限后可以按照智能相册或者用户相册形式获取相册列表。也可以直接获取所有图片

1
2
3
4
5
6
7
8
9
10
// 列出所有相册智能相册
let smartAlbums = PHAssetCollection.fetchAssetCollectionsWithType(PHAssetCollectionType.SmartAlbum, subtype: PHAssetCollectionSubtype.AlbumRegular, options: nil)

// 列出所有用户创建的相册
let topLevelUserCollections = PHCollectionList.fetchTopLevelUserCollectionsWithOptions(nil)

// 获取所有资源的集合,并按资源的创建时间排序
let options = PHFetchOptions()
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let assetsFetchResults = PHAsset.fetchAssetsWithOptions(nil)

相册类型有一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PHAssetCollectionSubtype : Int {
// PHAssetCollectionTypeAlbum regular subtypes
case AlbumRegular
case AlbumSyncedEvent
case AlbumSyncedFaces
case AlbumSyncedAlbum
case AlbumImported

// PHAssetCollectionTypeAlbum shared subtypes
case AlbumMyPhotoStream
case AlbumCloudShared

// PHAssetCollectionTypeSmartAlbum subtypes
case SmartAlbumGeneric
case SmartAlbumPanoramas
case SmartAlbumVideos
case SmartAlbumFavorites
case SmartAlbumTimelapses
case SmartAlbumAllHidden
case SmartAlbumRecentlyAdded
case SmartAlbumBursts
case SmartAlbumSlomoVideos
case SmartAlbumUserLibrary
@available(iOS 9.0, *)
case SmartAlbumSelfPortraits
@available(iOS 9.0, *)
case SmartAlbumScreenshots

// Used for fetching, if you don't care about the exact subtype
case Any
}

从相册里面获取资源列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let result = PHAssetCollection.fetchAssetCollectionsWithType(PHAssetCollectionType.SmartAlbum, subtype: PHAssetCollectionSubtype.AlbumRegular, options: nil)

// 从 PHFetchResult 获取相册的 PHAssetCollection
for i in 0..<result.count {
// PHFetchResult 可能包含 PHAsset 或者 PHAssetCollection,此时我们需要的是 PHAssetCollection
if let collection = result[i] as? PHAssetCollection {
let fetchOptions = PHFetchOptions()
// 按照创建时间排序
fetchOptions.sortDescriptors = [
NSSortDescriptor(key: "creationDate", ascending: false)
]
// 过滤出图片
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.Image.rawValue)

// 获取相册里面具体的资源列表
let result = PHAsset.fetchAssetsInAssetCollection(collection, options: fetchOptions)

} else {
"Fetch collection not PHCollection: \(result[i])"
}
}

从 PHAsset 读取具体的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
获取 Image 文件的 NSData,只需要在响应时候去获取再返回即可

- parameter url: 资源对应的请求url
- parameter callBack: 资源获取完成回调
*/
func fetchImageData(url:String, callBack:((data:NSData?)->Void)) {
if let asset = imageList[url] {
let options = PHImageRequestOptions()
options.synchronous = false
options.deliveryMode = .HighQualityFormat
options.networkAccessAllowed = true
PHImageManager.defaultManager().requestImageForAsset(asset,
targetSize: PHImageManagerMaximumSize,
contentMode: .Default,
options: options)
{ (result, info) -> Void in
if let image = result, data = UIImageJPEGRepresentation(image, 1.0) {
callBack(data:data)
} else {
callBack(data: nil)
}
}
} else {
callBack(data: nil)
}
}

/**
获取视频文件文件路径

- parameter url: 资源对应请求url
- parameter callBack: 资源获取完成回调
*/
func fetchVideoFile(url:String, callBack:((fileURL:String?)->Void)) {
if let asset = imageList[url] {
let imageManager = PHImageManager.defaultManager()
let videoRequestOptions = PHVideoRequestOptions()

videoRequestOptions.deliveryMode = .Automatic
videoRequestOptions.version = .Current
videoRequestOptions.networkAccessAllowed = true

imageManager.requestAVAssetForVideo(asset,
options: videoRequestOptions,
resultHandler:
{ (avAsset, avAudioMix, info) -> Void in

if let nextURLAsset = avAsset as? AVURLAsset,
filepath = nextURLAsset.URL.path {
callBack(fileURL: filepath)
} else {
callBack(fileURL: nil)
}
})
} else {
return callBack(fileURL: nil)
}
}

其中 deliveryMode 有以下选项。

1
2
3
4
5
6
public enum PHImageRequestOptionsDeliveryMode : Int {
case Opportunistic // 可能会多次调用闭包,先返回一个低质量,然后反复几次最终回掉高质量的。
case HighQualityFormat // 只会调用一次,返回高质量内容,但是可能比较耗时间
case FastFormat // 快速返回,但是质量低
}

参考