Osheep

时光不回头,当下最重要。

iOS开发之获取照片&&TZImagePickerController的使用

由于前段时间,需要完成一个跟相册的需求,所以阅读了一些与图片有关的文章,和使用了一些相关的第三方库。

在 iOS 设备中,照片和视频是相当重要的一部分。在 iOS 8 出现之前,开发者只能使用 AssetsLibrary 框架来访问设备的照片库,这是一个有点跟不上 iOS 应用发展步伐以及代码设计原则但确实强大的框架,考虑到 iOS7 仍占有不少的渗透率,因此 AssetsLibrary 也是本文重点介绍的部分。随着 iOS 8 的到来,苹果给我们提供了一个现代化的框架 —— PhotoKit,它比 AssetsLibrary 表现更好,并且拥有让应用和设备照片库无缝工作的特性。

另外值得强调的是,在 iOS 中,照片库并不只是照片的集合,同时也包含了视频。在 AssetsLibrary 中两者都有相同类型的对象去描述,只是类型不同而已。文中为了方便,大部分时候会使用「资源」代表 iOS 中的「照片和视频」。

PhotoKit 对象模型

PhotoKit 定义了与系统的 Photos 应用内展现给用户的模型对象相一致的实体图表。这些照片实体都是轻量级的不可变对象。所有的 PhotoKit 对象都是继承自 PHObject 抽象基类,其公共接口只提供了一个 localIdentifier 属性。

《iOS开发之获取照片&&TZImagePickerController的使用》

PHObject.png

PHAsset

表示用户照片库中一个单独的资源,用以提供资源的元数据。

成组的资源叫做资源集合,用 PHAssetCollection 类表示。一个单独的资源集合可以是照片库中的一个相册或者一个时刻,或者是一个特殊的“智能相册”。这种智能相册包括所有的视频集合,最近添加的项目,用户收藏,所有连拍照片等等。PHAssetCollectionPHCollection 的子类。

PHCollectionList 表示一组的 PHCollections。因为它本身就是 PHCollection,所以集合列表可以包含其他集合列表,它们允许复杂的集合继承。实际上,我们可以在照片应用的时刻栏目中看到它:照片 — 时刻 — 精选 — 年度,就是一个例子。

《iOS开发之获取照片&&TZImagePickerController的使用》

PHAsset.png

获取 (Fetch) 照片实体

获取 vs. 枚举
那些熟悉 AssetsLibrary 框架的开发者可能会记得 AssetsLibrary 可以用一些特定属性来找到需要的资源,其中一个必须枚举用户资源库来获得匹配的资源。不得不承认,这个 API 虽然提供了一些缩小搜索域的方法,但还是十分低效。
而与之形成鲜明对比,PhotoKit 实体的实例是通过获取得到的。那些熟悉 Core Data 的人,会觉得和 PhotoKit 在概念和描述都比较接近。
AssetsLibrary 的组成比较符合照片库本身的组成,照片库中的完整照片库对象、相册、相片都能在 AssetsLibrary 中找到一一对应的组成,这使到 AssetsLibrary 的使用变得直观而方便。

AssetsLibrary: 代表整个设备中的资源库(照片库),通过 AssetsLibrary 可以获取和包括设备中的照片和视频

  • ALAssetsGroup: 映射照片库中的一个相册,通过 ALAssetsGroup 可以获取某个相册的信息,相册下的资源,同时也可以对某个相册添加资源。
  • ALAsset: 映射照片库中的一个照片或视频,通过 ALAsset 可以获取某个照片或视频的详细信息,或者保存照片和视频。
  • ALAssetRepresentation: ALAssetRepresentation 是对 ALAsset 的封装(但不是其子类),可以更方便地获取 ALAsset 中的资源信息,每个 ALAsset 都有至少有一个 ALAssetRepresentation 对象,可以通过 defaultRepresentation 获取。而例如使用系统相机应用拍摄的 RAW + JPEG 照片,则会有两个 ALAssetRepresentation,一个封装了照片的 RAW 信息,另一个则封装了照片的 JPEG 信息。

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

获取结果
你可能已经注意到了这些获取操作不是异步的。它们返回了一个 PHFetchResult 对象,可以用类似 NSArray 的接口来访问结果内的集合。它会按需动态加载内容并且缓存最近请求的内容。这个行为和设置了 batchSize 属性的 NSFetchRequest 返回的结果数组相似。对于 PHFetchResult 来说,没有办法用参数来指定这个行为,但是官网文档保证 “即使在处理大量的返回结果时,依然能够有最好的表现”。

《iOS开发之获取照片&&TZImagePickerController的使用》

PHFetchResult.png

PHImageManager 照片加载

在处理用户照片库的过去几年中,开发者创造了上百 (如果没有上千) 的小技巧来提高照片加载和展示的效率。这些技巧处理请求的派发和取消,图像大小的修改和裁剪,缓存等等。PhotoKit 提供了一个可以用更加便捷和现代的 API 做了所有这些操作的类:PHImageManager

- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
- (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset options:(nullable PHImageRequestOptions *)options resultHandler:(void(^)(NSData *__nullable imageData, NSString *__nullable dataUTI, UIImageOrientation orientation, NSDictionary *__nullable info))resultHandler;

PHImageRequestOptions 控制加载图片时的参数

提供了一些方式来确定图像管理器该以怎样的方式来重新设置图像大小。
resizeMode 属性可以设置为 .Exact (返回图像必须和目标大小相匹配),.Fast (比 .Exact 效率更高,但返回图像可能和目标大小不一样) 或者 .None
还有个值得一提的是,normalizedCroppingMode 属性让我们确定图像管理器应该如何裁剪图像。注意:如果设置了 normalizedcroppingMode 的值,那么 resizeMode 需要设置为 .Exact

结果回调 (result handler)

结果回调是一个包含了一个 UIImage 变量和一个 info 字典作为参数的 block。根据参数和请求的选项,在请求的整个生命周期,它可以被图像管理器多次调用。

info 字典提供了关于当前请求状态的信息,比如:

图像是否必须从 iCloud 请求 (如果你初始化时将 networkAccessAllowed 设置成 false,那么就必须重新请求图像) —— PHImageResultIsInCloudKey
当前递送的 UIImage 是否是最终结果的低质量格式。当高质量图像正在下载时,这个可以让你给用户先展示一个预览图像 —— PHImageResultIsDegradedKey
请求 ID (可以便捷的取消请求),以及请求是否已经被取消 —— PHImageResultRequestIDKeyPHImageCancelledKey
如果没有图像提供给 result handler,字典内还会有一个错误信息 —— PHImageErrorKey

TZImagePickerController(1.5.0)使用方法

方法一:

TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithSelectedAssets:_selectedAssets selectedPhotos:_selectedPhotos index:indexPath.row];
imagePickerVc.allowPickingOriginalPhoto = self.allowPickingOriginalPhotoSwitch.isOn;
imagePickerVc.isSelectOriginalPhoto = _isSelectOriginalPhoto;
[imagePickerVc setDidFinishPickingPhotosHandle:^(NSArray<UIImage *> *photos, NSArray *assets, BOOL isSelectOriginalPhoto) {
    _selectedPhotos = [NSMutableArray arrayWithArray:photos];
    _selectedAssets = [NSMutableArray arrayWithArray:assets];
    _isSelectOriginalPhoto = isSelectOriginalPhoto;
    _layout.itemCount = _selectedPhotos.count;
    [_collectionView reloadData];
    _collectionView.contentSize = CGSizeMake(0, ((_selectedPhotos.count + 2) / 3 ) * (_margin + _itemWH));
}];

方法二:
首先遵守<TZImagePickerControllerDelegate>

TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:maxSelectCount delegate:self];
imagePickerVc.allowPickingOriginalPhoto = self.allowPickingOriginalPhotoSwitch.isOn;
imagePickerVc.isSelectOriginalPhoto = _isSelectOriginalPhoto;
imagePickerVc.sortAscendingByModificationDate = NO;
[self.navigationController presentViewController:imagePickerVc animated:YES completion:nil];

- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto {
    imagePickerVc.sortAscendingByModificationDate = NO;
    imagePickerVc.photoWidth = 1024.0;
    imagePickerVc.photoPreviewMaxWidth = 3072.0;
    [self.navigationController presentViewController:imagePickerVc animated:YES completion:nil];
}

注意:

1.在使用时,如果你不获取原图的话,TZImagePickerController的代理方法或block回调里返回的图片是质量非常差的图片,压缩程度非常高。因为它是直接把PHImageResultIsDegradedKey里的图片返回,从字面上我们就可以看出,这是苹果返回的一个退化的图片,我在前面也说了,这只是返回的一个预览图像,并且TZImagePickerController在创建PHImageRequestOptions的时候,resizeMode使用的是PHImageRequestOptionsResizeModeFast;如果把resizeMode修改成PHImageRequestOptionsResizeModeNone,可以适当的提高。
2.在外部设置photoPreviewMaxWidth超出500~800无效,因为TZImagePickerController在内部设置了范围。

- (void)setPhotoPreviewMaxWidth:(CGFloat)photoPreviewMaxWidth {
_photoPreviewMaxWidth = photoPreviewMaxWidth;
if (photoPreviewMaxWidth > 800) {
    _photoPreviewMaxWidth = 800;
} else if (photoPreviewMaxWidth < 500) {
    _photoPreviewMaxWidth = 500;
}
[TZImageManager manager].photoPreviewMaxWidth = _photoPreviewMaxWidth;
}

所以,如果需要设置的话注意一下。

参考:
iOS 开发之照片框架详解
objc中国-照片框架

点赞