相册管理

作者: 苏永茂 | 来源:发表于2016-07-05 16:13 被阅读710次

    图片视频管理 AssetsLibrary

    在ios8之前 ,只能使用AssetsLibrary 来访问设备的照片库 。
    ios8之后,苹果提供了PhotoKit 框架 。

    需要注意,在ios中 ,照片库中既包含 图片 ,也包含 视频 。

    --

    AssetsLibrary 组成介绍

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

    主要需要掌握的方法就是从相册加载图片

    • 首先是要检查 App 是否有照片操作授权:
    NSString *tipTextWhenNoPhotosAuthorization; // 提示语
    // 获取当前应用对照片的访问授权状态
    ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus];
    // 如果没有获取访问授权,或者访问授权状态已经被明确禁止,则显示提示语,引导用户开启授权
    if (authorizationStatus == ALAuthorizationStatusRestricted || authorizationStatus == ALAuthorizationStatusDenied) {
        NSDictionary *mainInfoDictionary = [[NSBundle mainBundle] infoDictionary];
        NSString *appName = [mainInfoDictionary objectForKey:@"CFBundleDisplayName"];
        tipTextWhenNoPhotosAuthorization = [NSString stringWithFormat:@"请在设备的\"设置-隐私-照片\"选项中,允许%@访问你的手机相册", appName];
        // 展示提示语
    }
    
    • 获取相册列表
    _assetsLibrary = [[ALAssetsLibrary alloc] init];
    _albumsArray = [[NSMutableArray alloc] init];
    [_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
        if (group) {
            [group setAssetsFilter:[ALAssetsFilter allPhotos]];
            if (group.numberOfAssets > 0) {
                // 把相册储存到数组中,方便后面展示相册时使用
                [_albumsArray addObject:group];
            }
        } else {
            if ([_albumsArray count] > 0) {
                // 把所有的相册储存完毕,可以展示相册列表
            } else {
                // 没有任何有资源的相册,输出提示
            }
        }
    } failureBlock:^(NSError *error) {
        NSLog(@"Asset group not found!\n");
    }];
    

    这里需要的主意

    1 iOS 中允许相册为空,即相册中没有任何资源,如果不希望获取空相册,则需要像上面的代码中那样手动过滤
    2 ALAssetsGroup 有一个 setAssetsFilter 的方法,可以传入一个过滤器,控制只获取相册中的照片或只获取视频。一旦设置过滤,ALAssetsGroup 中资源列表和资源数量的获取也会被自动更新。
    3 整个 AssetsLibrary 中对相册、资源的获取和保存都是使用异步处理(Asynchronous),这是考虑到资源文件体积相当比较大(还可能很大)。例如上面的遍历相册操作,相册的结果使用 block 输出,如果相册遍历完毕,则最后一次输出的 block 中的 group 参数值为 nil。而 stop 参数则是用于手工停止遍历,只要把 *stop 置 YES,则会停止下一次的遍历。关于这一点常常会引起误会,所以需要注意。

    • 接下来是获取相册中的资源:
    _imagesAssetArray = [[NSMutableArray alloc] init];
    [assetsGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
        if (result) {
            [_imagesAssetArray addObject:result];
        } else {
            // result 为 nil,即遍历相片或视频完毕,可以展示资源列表
        }
    }];
    
    

    跟遍历相册的过程类似,遍历相片也是使用一系列的异步方法,其中上面的方法所输出的 block 中,除了 result 参数表示资源信息,stop 用于手工停止遍历外,还提供了一个 index 参数,这个参数表示资源的索引。一般来说,展示资源列表都会使用缩略图(result.thumbnail),因此即使资源很多,遍历资源的速度也会相当快。但如果确实需要加载资源的高清图或者其他耗时的处理,则可以利用上面的 index 参数和 stop 参数做一个分段拉取资源。例如:

    NSUInteger _targetIndex; // index 目标值,拉取资源直到这个值就手工停止拉取
    NSUInteger _currentIndex; // 当前 index,每次拉取资源时从这个值开始
     
    _targetIndex = 50;
    _currentIndex = 0;
     
    - (void)loadAssetWithAssetsGroup:(assetsGroup *)assetsGroup {
        [assetsGroup enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:_currentIndex] options:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
            _currentIndex = index;
            if (index > _targetIndex) {
                // 拉取资源的索引如果比目标值大,则停止拉取
                *stop = YES;
            } else {
                if (result) {
                    [_imagesAssetArray addObject:result];
                } else {
                    // result 为 nil,即遍历相片或视频完毕
                }
            }
        }];
    }
     
    // 之前拉取的数据已经显示完毕,需要展示新数据,重新调用 loadAssetWithAssetsGroup
    
    • 最后是获取图片的详情
    // 获取资源图片的详细资源信息,其中 imageAsset 是某个资源的 ALAsset 对象
    ALAssetRepresentation *representation = [imageAsset defaultRepresentation];
    // 获取资源图片的 fullScreenImage
    UIImage *contentImage = [UIImage imageWithCGImage:[representation fullScreenImage]];
    
    

    对于一个 ALAssetRepresentation,里面包含了图片的多个版本。最常用的是 fullResolutionImage 和 fullScreenImage。fullResolutionImage 是图片的原图,通过 fullResolutionImage 获取的图片没有任何处理,包括通过系统相册中“编辑”功能处理后的信息也没有被包含其中,因此需要展示“编辑”功能处理后的信息,使用 fullResolutionImage 就比较不方便,另外 fullResolutionImage 的拉取也会比较慢,在多张 fullResolutionImage 中切换时能明显感觉到图片的加载过程。因此这里建议获取图片的 fullScreenImage,它是图片的全屏图版本,这个版本包含了通过系统相册中“编辑”功能处理后的信息,同时也是一张缩略图,但图片的失真很少,缺点是图片的尺寸是一个适应屏幕大小的版本,因此展示图片时需要作出额外处理,但考虑到加载速度非常快的原因(在多张图片之间切换感受不到图片加载耗时),仍建议使用 fullScreenImage。

    系统相册的处理过程大概也是如上,可以看出,在整个过程中并没有使用到图片的 fullResolutionImage,从相册列表展示到最终查看资源,都是使用缩略图,这也是 iOS 相册加载快的一个重要原因。

    --

    补充

    获取原图
    CGImageRef fullResolutionImageRef = [[(ALAsset *)asset defaultRepresentation] fullResolutionImage];
    //        // 通过 fullResolutionImage 获取到的的高清图实际上并不带上在照片应用中使用“编辑”处理的效果,需要额外在 AlAssetRepresentation 中获取这些信息
            NSString *adjustment = [[[(ALAsset *)asset defaultRepresentation] metadata] objectForKey:@"AdjustmentXMP"];
            if (adjustment) {
                // 如果有在照片应用中使用“编辑”效果,则需要获取这些编辑后的滤镜,手工叠加到原图中
                NSData *xmpData = [adjustment dataUsingEncoding:NSUTF8StringEncoding];
                CIImage *tempImage = [CIImage imageWithCGImage:fullResolutionImageRef];
                
                NSError *error;
                NSArray *filterArray = [CIFilter filterArrayFromSerializedXMP:xmpData
                                                             inputImageExtent:tempImage.extent
                                                                        error:&error];
                CIContext *context = [CIContext contextWithOptions:nil];
                if (filterArray && !error) {
                    for (CIFilter *filter in filterArray) {
                        [filter setValue:tempImage forKey:kCIInputImageKey];
                        tempImage = [filter outputImage];
                    }
                    fullResolutionImageRef = [context createCGImage:tempImage fromRect:[tempImage extent]];
                }
            }
            // 生成最终返回的 UIImage,同时把图片的 orientation 也补充上去
            resultImage = [UIImage imageWithCGImage:fullResolutionImageRef
                                              scale:[[asset defaultRepresentation] scale]
                                        orientation:(UIImageOrientation)[[asset defaultRepresentation] orientation]];
    

    带一个block参数 作为输入值 ,函数之行到block 。就会调用block 。也就是回调block 。

        `[[XMNPhotoManager sharedManager] getOriginImageWithAsset:self.asset completionBlock:^(UIImage *image){
        resultImage = image;
    }];
    

    `

    - (void)getOriginImageWithAsset:(id _Nonnull)asset completionBlock:(void(^_Nonnull)(UIImage * _Nullable image))completionBlock;
    这段代码就是manger 之行方法,会调用block (这个block 需要一个image输入值)。

    manger 就是 用一个image 来调用block 。最后block 就回调他自己的代码块 { resultImage = image; }];
    实现了image 从 manger 到其它类的 异步传递 。

    相册管理 PhotoKit

    • PHAsset: 代表照片库中的一个资源,跟 ALAsset 类似,通过 PHAsset 可以获取和保存资源
    • PHFetchOptions: 获取资源时的参数,可以传 nil,即使用系统默认值
    • PHFetchResult: 表示一系列的资源集合,也可以是相册的集合
    • PHAssetCollection: 表示一个相册或者一个时刻,或者是一个「智能相册(系统提供的特定的一系列相册,例如:最近删除,视频列表,收藏等等)
    • PHImageManager: 用于处理资源的加载,加载图片的过程带有缓存处理,可以通过传入一个 PHImageRequestOptions 控制资源的输出尺寸等规格
    • PHImageRequestOptions: 如上面所说,控制加载图片时的一系列参数
    // 列出所有相册智能相册
    PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
     
    // 列出所有用户创建的相册
    PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
     
    // 获取所有资源的集合,并按资源的创建时间排序
    PHFetchOptions *options = [[PHFetchOptions alloc] init];
    options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
    PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
     
    // 在资源的集合中获取第一个集合,并获取其中的图片
    PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init];
    PHAsset *asset = assetsFetchResults[0];
    [imageManager requestImageForAsset:asset
                             targetSize:SomeSize
                            contentMode:PHImageContentModeAspectFill
                                options:nil
                          resultHandler:^(UIImage *result, NSDictionary *info) {
                               
                              // 得到一张 UIImage,展示到界面上
                               
                          }];
    
    • 从 AssetsLibrary 中获取数据,无论是相册,还是资源,本质上都是使用枚举的方式,遍历照片库取得相应的数据。而 PhotoKit 则是通过传入参数,直接获取相应的数据,因而效率会提高不少。
    • 在 AssetsLibrary 中,相册和资源是对应不同的对象(ALAssetGroup 和 ALAsset),因此获取相册和获取资源是两个完全没有关联的接口。而 PhotoKit 中则有 PHFetchResult 这个可以统一储存相册或资源的对象,因此处理相册和资源时也会比较方便。
    • PhotoKit 返回资源结果时,同时返回了资源的元数据,获取元数据在 AssetsLibrary 中是很难办到的一件事。同时通过 PHAsset,开发者还能直接获取资源是否被收藏(favorite)和隐藏(hidden),拍摄图片时是否开启了 HDR 或全景模式,甚至能通过一张连拍图片获取到连拍图片中的其他图片。这也是文章开头说的,PhotoKit 能更好地与设备照片库接入的一个重要因素。

    一个很棒的图片选择器

    相关文章

      网友评论

        本文标题:相册管理

        本文链接:https://www.haomeiwen.com/subject/roihjttx.html