• iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)


    本文链接:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html

    这里接着前文《iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)》,主要是干货环节,列举了如何基于 PhotoKit 与 AlAssetLibrary 封装出通用的方法。

    三. 常用方法的封装

    虽然 PhotoKit 的功能强大很多,但基于兼容 iOS 8.0 以下版本的考虑,暂时可能仍无法抛弃 ALAssetLibrary,这时候一个比较好的方案是基于 ALAssetLibrary 和 PhotoKit 封装出一系列模拟系统 Asset 类的自定义类,然后在其中封装好兼容 ALAssetLibrary 和 PhotoKit 的方法。

    这里列举了四种常用的封装好的方法:原图,缩略图,预览图,方向,下面直接上代码,代码中有相关注释解释其中的要点。其中下面的代码中常常出现的 [[QMUIAssetsManager sharedInstance] phCachingImageManager] 是 QMUI 框架中封装的类以及单例方法,表示产生一个 PHCachingImageManager 的单例,这样做的好处是 PHCachingImageManager 需要占用较多的资源,因此使用单例可以避免无谓的资源消耗,另外请求图像等方法需要基于用一个 PHCachingImageManager 实例才能进行进度续传,管理请求等操作。

    1. 原图

    由于原图的尺寸通常会比较大,因此建议使用异步拉取,但这里仍同时列举同步拉取的方法。这里需要留意如前文中所述,ALAssetRepresentation 中获取原图的接口 fullResolutionImage 所得到的图像并没有带上系统相册“编辑”(选中,滤镜等)的效果,需要额外获取这些效果并手工叠加到图像上。

    .h 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /// Asset 的原图(包含系统相册“编辑”功能处理后的效果)
    - (UIImage *)originImage;
     
    /**
     *  异步请求 Asset 的原图,包含了系统照片“编辑”功能处理后的效果(剪裁,旋转和滤镜等),可能会有网络请求
     *
     *  @param completion        完成请求后调用的 block,参数中包含了请求的原图以及图片信息,在 iOS 8.0 或以上版本中,
     *                           这个 block 会被多次调用,其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直接获取到高清图,
     *                           获取到高清图后 QMUIAsset 会缓存起这张高清图,这时 block 中的第二个参数(图片信息)返回的为 nil。
     *  @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
     *
     *  @wraning iOS 8.0 以下中并没有异步请求预览图的接口,因此实际上为同步请求,这时 block 中的第二个参数(图片信息)返回的为 nil。
     *
     *  @return 返回请求图片的请求 id
     */
    - (NSInteger)requestOriginImageWithCompletion:(void (^)(UIImage *, NSDictionary *))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler;

     .m 文件

    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
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    - (UIImage *)originImage {
        if (_originImage) {
            return _originImage;
        }
        __block UIImage *resultImage;
        if (_usePhotoKit) {
            PHImageRequestOptions *phImageRequestOptions = [[PHImageRequestOptions alloc] init];
            phImageRequestOptions.synchronous = YES;
            [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
                                                                                  targetSize:PHImageManagerMaximumSize
                                                                                 contentMode:PHImageContentModeDefault
                                                                                     options:phImageRequestOptions
                                                                               resultHandler:^(UIImage *result, NSDictionary *info) {
                                                                                   resultImage = result;
                                                                               }];
        } else {
            CGImageRef fullResolutionImageRef = [_alAssetRepresentation fullResolutionImage];
            // 通过 fullResolutionImage 获取到的的高清图实际上并不带上在照片应用中使用“编辑”处理的效果,需要额外在 AlAssetRepresentation 中获取这些信息
            NSString *adjustment = [[_alAssetRepresentation 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:[_alAssetRepresentation scale] orientation:(UIImageOrientation)[_alAssetRepresentation orientation]];
        }
        _originImage = resultImage;
        return resultImage;
    }
     
    - (NSInteger)requestOriginImageWithCompletion:(void (^)(UIImage *, NSDictionary *))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
        if (_usePhotoKit) {
            if (_originImage) {
                // 如果已经有缓存的图片则直接拿缓存的图片
                if (completion) {
                    completion(_originImage, nil);
                }
                return 0;
            } else {
                PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
                imageRequestOptions.networkAccessAllowed = YES; // 允许访问网络
                imageRequestOptions.progressHandler = phProgressHandler;
                return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
                    // 排除取消,错误,低清图三种情况,即已经获取到了高清图时,把这张高清图缓存到 _originImage 中
                    BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
                    if (downloadFinined) {
                        _originImage = result;
                    }
                    if (completion) {
                        completion(result, info);
                    }
                }];
            }
        } else {
            if (completion) {
                completion([self originImage], nil);
            }
            return 0;
        }
    }

     2. 缩略图

    相对于在拉取原图时 ALAssetLibrary 的部分需要手工叠加系统相册的“编辑”效果,拉取缩略图则简单一些,因为系统接口拉取到的缩略图已经带上“编辑”的效果了。

    .h 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
     *  Asset 的缩略图
     *
     *  @param size 指定返回的缩略图的大小,仅在 iOS 8.0 及以上的版本有效,其他版本则调用 ALAsset 的接口由系统返回一个合适当前平台的图片
     *
     *  @return Asset 的缩略图
     */
    - (UIImage *)thumbnailWithSize:(CGSize)size;
     
    /**
     *  异步请求 Asset 的缩略图,不会产生网络请求
     *
     *  @param size       指定返回的缩略图的大小,仅在 iOS 8.0 及以上的版本有效,其他版本则调用 ALAsset 的接口由系统返回一个合适当前平台的图片
     *  @param completion 完成请求后调用的 block,参数中包含了请求的缩略图以及图片信息,在 iOS 8.0 或以上版本中,这个 block 会被多次调用,
     *                    其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直接获取到高清图,获取到高清图后 QMUIAsset 会缓存起这张高清图,
     *                    这时 block 中的第二个参数(图片信息)返回的为 nil。
     *
     *  @return 返回请求图片的请求 id
     */
    - (NSInteger)requestThumbnailImageWithSize:(CGSize)size completion:(void (^)(UIImage *, NSDictionary *))completion;

    .m 文件

    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
    - (UIImage *)thumbnailWithSize:(CGSize)size {
        if (_thumbnailImage) {
            return _thumbnailImage;
        }
        __block UIImage *resultImage;
        if (_usePhotoKit) {
            PHImageRequestOptions *phImageRequestOptions = [[PHImageRequestOptions alloc] init];
            phImageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
                // 在 PHImageManager 中,targetSize 等 size 都是使用 px 作为单位,因此需要对targetSize 中对传入的 Size 进行处理,宽高各自乘以 ScreenScale,从而得到正确的图片
            [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
                                                                                  targetSize:CGSizeMake(size.width * ScreenScale, size.height * ScreenScale)
                                                                                 contentMode:PHImageContentModeAspectFill options:phImageRequestOptions
                                                                               resultHandler:^(UIImage *result, NSDictionary *info) {
                                                                                   resultImage = result;
                                                                               }];
        } else {
            CGImageRef thumbnailImageRef = [_alAsset thumbnail];
            if (thumbnailImageRef) {
                resultImage = [UIImage imageWithCGImage:thumbnailImageRef];
            }
        }
        _thumbnailImage = resultImage;
        return resultImage;
    }
     
    - (NSInteger)requestThumbnailImageWithSize:(CGSize)size completion:(void (^)(UIImage *, NSDictionary *))completion {
        if (_usePhotoKit) {
            if (_thumbnailImage) {
                if (completion) {
                    completion(_thumbnailImage, nil);
                }
                return 0;
            } else {
                PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
                imageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
                // 在 PHImageManager 中,targetSize 等 size 都是使用 px 作为单位,因此需要对targetSize 中对传入的 Size 进行处理,宽高各自乘以 ScreenScale,从而得到正确的图片
                return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:CGSizeMake(size.width * ScreenScale, size.height * ScreenScale) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
                    // 排除取消,错误,低清图三种情况,即已经获取到了高清图时,把这张高清图缓存到 _thumbnailImage 中
                      BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
                      if (downloadFinined) {
                          _thumbnailImage = result;
                      }
                      if (completion) {
                          completion(result, info);
                      }
                }];
            }
        } else {
            if (completion) {
                completion([self thumbnailWithSize:size], nil);
            }
            return 0;
        }
    }

     3. 预览图

    与上面的方法类似,不再展开说明。

    .h 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
     *  Asset 的预览图
     *
     *  @warning 仿照 ALAssetsLibrary 的做法输出与当前设备屏幕大小相同尺寸的图片,如果图片原图小于当前设备屏幕的尺寸,则只输出原图大小的图片
     *  @return Asset 的全屏图
     */
    - (UIImage *)previewImage;
     
    /**
     *  异步请求 Asset 的预览图,可能会有网络请求
     *
     *  @param completion        完成请求后调用的 block,参数中包含了请求的预览图以及图片信息,在 iOS 8.0 或以上版本中,
     *                           这个 block 会被多次调用,其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直接获取到高清图,
     *                           获取到高清图后 QMUIAsset 会缓存起这张高清图,这时 block 中的第二个参数(图片信息)返回的为 nil。
     *  @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
     *
     *  @wraning iOS 8.0 以下中并没有异步请求预览图的接口,因此实际上为同步请求,这时 block 中的第二个参数(图片信息)返回的为 nil。
     *
     *  @return 返回请求图片的请求 id
     */
    - (NSInteger)requestPreviewImageWithCompletion:(void (^)(UIImage *, NSDictionary *))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler;

     .m 文件

    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
    - (UIImage *)previewImage {
        if (_previewImage) {
            return _previewImage;
        }
        __block UIImage *resultImage;
        if (_usePhotoKit) {
            PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
            imageRequestOptions.synchronous = YES;
            [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
                                                                                targetSize:CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT)
                                                                               contentMode:PHImageContentModeAspectFill
                                                                                   options:imageRequestOptions
                                                                             resultHandler:^(UIImage *result, NSDictionary *info) {
                                                                                 resultImage = result;
                                                                             }];
        } else {
            CGImageRef fullScreenImageRef = [_alAssetRepresentation fullScreenImage];
            resultImage = [UIImage imageWithCGImage:fullScreenImageRef];
        }
        _previewImage = resultImage;
        return resultImage;
    }
     
    - (NSInteger)requestPreviewImageWithCompletion:(void (^)(UIImage *, NSDictionary *))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
        if (_usePhotoKit) {
            if (_previewImage) {
                // 如果已经有缓存的图片则直接拿缓存的图片
                if (completion) {
                    completion(_previewImage, nil);
                }
                return 0;
            } else {
                PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
                imageRequestOptions.networkAccessAllowed = YES; // 允许访问网络
                imageRequestOptions.progressHandler = phProgressHandler;
                return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
                    // 排除取消,错误,低清图三种情况,即已经获取到了高清图时,把这张高清图缓存到 _previewImage 中
                    BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
                    if (downloadFinined) {
                        _previewImage = result;
                    }
                    if (completion) {
                        completion(result, info);
                    }
                }];
            }
        } else {
            if (completion) {
                completion([self previewImage], nil);
            }
            return 0;
        }
    }

     4. 方向(imageOrientation)

    比较奇怪的是,无论在 PhotoKit 或者是 ALAssetLibrary 中,要想获取到准确的图像方向,只能通过某些 key 检索所得。

    .h 文件

    1
    - (UIImageOrientation)imageOrientation;

    .m 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    - (UIImageOrientation)imageOrientation {
        UIImageOrientation orientation;
        if (_usePhotoKit) {
            if (!_phAssetInfo) {
                // PHAsset 的 UIImageOrientation 需要调用过 requestImageDataForAsset 才能获取
                [self requestPhAssetInfo];
            }
            // 从 PhAssetInfo 中获取 UIImageOrientation 对应的字段
            orientation = (UIImageOrientation)[_phAssetInfo[@"orientation"] integerValue];
        } else {
            orientation = (UIImageOrientation)[[_alAsset valueForProperty:@"ALAssetPropertyOrientation"] integerValue];
        }
        return orientation;
    }

    系列文章:
    iOS 开发之照片框架详解
    iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)
    iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

    参考资料:
    objc中国 - 照片框架
    Example app using Photos framework
    AssetsLibrary Framework Reference

     

  • 相关阅读:
    Vue笔记:使用 vuex 管理应用状态
    Vue + Element UI 实现权限管理系统(更换皮肤主题)
    Vue + Element UI 实现权限管理系统(优化登录流程)
    Vue + Element UI 实现权限管理系统(国际化实现)
    Vue笔记:使用 axios 中 this 指向问题
    Vue + Element UI 实现权限管理系统(工具模块封装)
    Vue + Element UI 实现权限管理系统(搭建开发环境)
    Vue 全家桶
    android 检测ListView滚动到的位置
    android 工具类之SharePreference
  • 原文地址:https://www.cnblogs.com/Jenaral/p/5381542.html
Copyright © 2020-2023  润新知