PHImageManager

现在的app中,对照片和视频的操作非常频繁,如:各类软件个人图像的选取,社交软件照片及视频的分享,评论上图等。。。IOS8中更新的PHImageManager对于想要操控手机图片或视频的程序猿们无疑是最好的选择。

获取手机照片并回显
大家都应该用过手机QQ,当我们要用QQ发送图片给小伙伴时,点击发送图片icon,QQ会非常贴心的将最近几张照片给我们呈现出来,供我们选择。用PHImageManager能很快的实现这样一个功能。在此做个简单的类似的实现:

  1. 在项目build phase中添加Libraries Photos.framework 并在vc中引入头文件

    
    #import <Photos/Photos.h>   
    
  2. 获取访问照片权限

    - (void)requestPermissons
    {
    //获取当前状态:PHAuthorizationStatusNotDetermined--未知
    //            PHAuthorizationStatusRestricted--受限制的(用户不能更改某个app的此状态,或许某些活动限制设置能改变如:家长模式)
    //            PHAuthorizationStatusDenied--拒绝访问
    //            PHAuthorizationStatusAuthorized--可以访问
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
    __weak RCCachingLocalImage *weakSelf = self;
    if (status == PHAuthorizationStatusNotDetermined) {
        [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
            if (status == PHAuthorizationStatusAuthorized) {
                //获取手机照片库
                //[weakSelf loadImages];
            }else{
                //弹出提示信息
                [weakSelf alertMsg];
            }
        }];
    }else if(status == PHAuthorizationStatusAuthorized){
        //获取手机照片库
        //[self loadImages];
    }else{
        //弹出提示信息
        [self alertMsg];
    }
    }
  3. 构建这样一个场景:在当前viewcontroller中点击button 然后全屏显示最新拍摄的照片或者截屏图片。UI代码如下:

    在Extension中定义全局变量

    @interface  RCCachingLocalImage()
    @property (nonatomic, strong) NSMutableArray *assets;//用于存储所有的照片。
    @property (nonatomic, strong) UIButton *showImgBnt;
    @property (nonatomic, strong) UIImageView *imgView;
    @end

    然后再.m中加入

    - (void)viewDidLoad
    {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self requestPermissons];
    
    UIButton *navButton = [UIButton new];
    navButton.frame = CGRectMake(130, CGRectGetHeight(self.view.frame) - 200,  CGRectGetWidth(self.view.frame)-2*130, 40);
    navButton.backgroundColor = [UIColor blueColor];
    [navButton setTitle:@"Show Image" forState:UIControlStateNormal];
    [navButton addTarget:self action:@selector(showImage) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:navButton];
    self.showImgBtn = navButton;
    
    UIImageView *imageView = [UIImageView new];
    imageView.backgroundColor = [UIColor greenColor];
    imageView.frame = self.view.frame;
    [self.view addSubview:imageView];
    self.imgView = imageView;
    self.imgView.hidden = YES;
    }
  4. 实现showImage方法 显示图片,隐藏Button

- (void)showImage
{
    self.showImgBnt.hidden = YES;
    self.imgView.hidden = NO;
    //获取相册里所有的照片
    PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
    self.assets = [[NSMutableArray alloc] init];
    [result enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj isKindOfClass:[PHAsset class]]) {
            [self.assets addObject:obj];
        }
    }];
    //最后一张图为最新
    PHAsset *asset = [self.assets lastObject];
    PHImageManager *manager = [PHImageManager defaultManager];

    [manager requestImageForAsset:asset
                       targetSize:PHImageManagerMaximumSize
                      contentMode:PHImageContentModeAspectFill
                          options:nil
                    resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
                        [self.imgView setImage:result];
                    }];
}

有两个方法看着比较陌生
第一个:

+ (PHFetchResult<PHAsset *> *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(nullable PHFetchOptions *)options;

该方法用作遍历资源库 返回特定类型的资源,mediaType可选四个值,对应关系分别为:PHAssetMediaTypeUnknown–所有 PHAssetMediaTypeImage–图片 PHAssetMediaTypeVideo–视频 PHAssetMediaTypeAudio–音频。
PHFetchResult对应一组资源,PHAsset对应单个资源。
options是搜索条件 如:搜索自己喜爱的照片 并且按照时间上升顺序排列:

    PHFetchOptions *option = [[PHFetchOptions alloc] init];
    option.predicate = [NSPredicate predicateWithFormat:@"favorite == YES"];
    option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
    PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:option];

第二个:

- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;

该方法用作获取特定的PHAsset所对应的图片,返回值为PHImageRequestID类型(实际上是32位int类型)的ID,可以用作取消加载还未加载完成的该请求。- (void)cancelImageRequest:(PHImageRequestID)requestID;
targetSize是你所需要返回的image尺寸,PHImageManagerMaximumSize为原图片尺寸
contentMode是如果原image尺寸与targetSize不相等,用哪种填充的mode,有两个可选值:PHImageContentModeAspectFill–缩放长宽完全填充 PHImageContentModeAspectFit–只缩放长宽中比较大的那个,来等于targetSize。
options:定义请求方式,可为nil,下文会详细介绍,
resultHandler:block 当image完全请求过来后调用。

现在 运行程序就能看到效果啦。

获取图片并缓存
如果需求为要在一个页面,同时显示大量图片,for循环调用上诉方法肯定是不行的,requestImageForAsset会有延时,而达到这一效果,我们可以先缓存下来所有的图片,然后用requestImageForAsset,根据PHImageManage的加载机制,能做到同时显示。
缓存图片方法代码如下:

- (void)loadImages
{
    NSLog(@"loadImages");
    PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
    NSLog(@"%lu",[result count]);
    self.assets = [[NSMutableArray alloc] init];
    [result enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj isKindOfClass:[PHAsset class]]) {
            [self.assets addObject:obj];
        }
    }];
    PHCachingImageManager *cachingImage = [[PHCachingImageManager alloc] init];
    [cachingImage startCachingImagesForAssets:self.assets
                                   targetSize:PHImageManagerMaximumSize
                                  contentMode:PHImageContentModeAspectFit
                                      options:nil];
}

经过此缓存后,再用RequestImageForAsset:targetSize:contentMode:options:resultHandler:方法时便能直接从缓存中加载image,从而直接执行resultHandler内的block,特别注意,无延时加载的前提条件是,此时的targetSize 与 缓存时的targetSize必须一样,不然此方法会去图片库重新请求资源加载成image而不会走缓存。

图片请求回显方式详解 PHImageRequestOptions
之前的RequestImageForAsset 和 startCachingImagesForAssets两个方法中 option都设置的是nil。PHImageRequestOptions是用来控制加载什么一级用什么样的方式。下面介绍其主要属性:
* networkAccessAllowed Bool 如果需要,是否允许从iCloud下载图片。
* normalizedCropRect CGRect 指定一个rect来截取原图片,rect以原图片为坐标系。
* synchronous Bool 同步加载。默认是NO。(只有当resultHandler block执行完后 该方法才执行完毕)。
* deliveryMode PHImageRequestOptionsDeliveryMode
* PHImageRequestOptionsDeliveryMode是一个enum类型的,它囊括了非常复杂的操作,有三个值分别对应:

  • PHImageRequestOptionsDeliveryModeOpportunistic 当选用此项时,Photos会在你请求时给你提供一个或者多个结果,这就意味着resultHandler block可能会执行一次或多次,例如
    Photos会先给你一个低分辨率的图片让你暂时显示,然后加载出高质量的图片后再次给你。如果PHImageManager已经pre-cache了图片,那result handler便只会执行一次。另外,如果synchronous属性为NO,此选项是不起作用的。
  • PHImageRequestOptionsDeliveryModeHighQualityFormat 当请求image时 不管请求需要多长时间完成,Photos只会提供高质量的image。另外,当synhronous属性为YES 或者使用requestImageDataForAsset:options:resultHandler: 方法时,这个选项是默认且唯一的。
  • PHImageRequestOptionsDeliveryModeFastFormat 当请求image时,高质量的图片或低质量的图片,谁加载的快 便显示谁。另外Photos也能通过检查info字典(resultHandler中的参数)里的PHImageResultIsDegradedKey值来判定传高质量或者低质量的图片。

之前运行时,应该能发现 在图片出现前会有短暂的图片背景色显示。现在我们加上option后再请求图片:

- (void)showImage
{
    self.showImgBnt.hidden = YES;
    self.imgView.hidden = NO;
    PHAsset *asset = [self.assets firstObject];
    PHImageManager *manager = [PHImageManager defaultManager];
    PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
    option.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
    option.synchronous = YES;
    [manager requestImageForAsset:asset
                       targetSize:PHImageManagerMaximumSize
                      contentMode:PHImageContentModeAspectFill options:option
                    resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
                        [self.imgView setImage:result];
                    }];
}

此时便不会有那个问题了。

ps:关于PHImageRequestOptionsDeliveryMode enum中值的意思我是翻译的apple官方文档,其中有一个逻辑错误就是,按照官方文档逻辑来看,在任何情况下PHImageRequestOptionsDeliveryModeOpportunistic属性都不会起作用。对此问题我也google过,然后发现mattt大大的文章中也是直接搬过来的,在Twitter上问了他,问题确实存在,以下是我与他的私信过程:
这里写图片描述

struct VideoPicker: UIViewControllerRepresentable { @Environment(.presentationMode) private var presentationMode let sourceType: UIImagePickerController.SourceType // let onImagePicked: (UIImage) -> Void let onURLPicked: (URL) -> Void final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { @Binding private var presentationMode: PresentationMode private let sourceType: UIImagePickerController.SourceType private let onURLPicked: (URL) -> Void init(presentationMode: Binding<PresentationMode>, sourceType: UIImagePickerController.SourceType, onURLPicked: @escaping (URL) -> Void) { presentationMode = presentationMode self.sourceType = sourceType self.onURLPicked = onURLPicked } func imagePickerController( picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { // let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage // onImagePicked(uiImage) if let url = info[.mediaURL] as? URL{ onURLPicked(url) } presentationMode.dismiss() } func getVideoURL(from assetIdentifier: String, completion: @escaping (URL?) -> Void) { let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil) guard let asset = fetchResult.firstObject else { completion(nil) return } let options = PHVideoRequestOptions() options.version = .original PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avAsset, , _ in guard let avAsset = avAsset else { completion(nil) return } let url = (avAsset as? AVURLAsset)?.url completion(url) } } func imagePickerControllerDidCancel( picker: UIImagePickerController) { presentationMode.dismiss() } } func makeCoordinator() -> Coordinator { return Coordinator(presentationMode: presentationMode, sourceType: sourceType, onURLPicked: onURLPicked) } func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIImagePickerController { let picker = UIImagePickerController() picker.sourceType = sourceType picker.delegate = context.coordinator picker.mediaTypes = ["public.movie"] return picker } func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<VideoPicker>) { } }获取的url内的路径不可用
05-24
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值