天天看点

iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)

前言

保护用户的隐私不被侵害是每个开发者应该承担的责任

用户隐私已经不断被资产化,通过商业运作成为了一种牟利的手段。apple在用户隐私保护方面一直走在前列.​

​遵循使用最少的权限来实现功能​

例如给全部权限的话,有些app就偷偷读相册,根据相册给推商品

在iOS14相册 iOS 14 相册权限增加了 Limited Photo 模式 ,新增选择权限类型​

​ PHAuthorizationStatusLimited​

14)), // User has authorized this application for limited photo library access. Add PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YES to the application's Info.plist to prevent the automatic alert to update the users limited library selection. Use -[PHPhotoLibrary(PhotosUISupport) presentLimitedLibraryPickerFromViewController:] from PhotosUI/PHPhotoLibrary+PhotosUISupport.h to manually present the limited library picker.      
iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)

I iOS14相册权限适配

预备知识:

冷启动: App 不在内存中/没有相关的进程存在

热启动:(从后台恢复)内存有部分,无相关的进程存在

暂停:存在内存中,存在相关进程

1.1 选择允许被访问的图片资源

用户在冷启 APP 使用 PhotoKit 访问资源的时候会默认弹出系统图片选择界面,让用户选择允许被访问的资源:

当页面弹出请求权限 Alert 时,会有​

​选择照片...​

​选项,用户选择该选项时,会弹出页面供用户选择允许App访问的照片。

iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)

后续有两种方式来修改用户选择的资源:

  1. 手动触发选择/取消选择图片以移除访问权限的界面 :​

    ​ [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];​

iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)
  1. 在应用设置->Photos->Edit Selected Photos中修改

部分相册权限时,界面提供直接跳设置的入口

iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)
iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)

1.2 相册权限API的相关改动

  • 新增了资源申请和获取的 API,并且将老的资源申请 API 标为废弃
/// Replaces \c +authorizationStatus to support add-only/read-write access level status
+ (PHAuthorizationStatus)authorizationStatusForAccessLevel:(PHAccessLevel)accessLevel API_AVAILABLE(macosx(11.0), ios(14), tvos(14));
+ (void)requestAuthorizationForAccessLevel:(PHAccessLevel)accessLevel handler:(void(^)(PHAuthorizationStatus status))handler API_AVAILABLE(macosx(11.0), ios(14), tvos(14)) NS_SWIFT_ASYNC(2);      
  • Limited Photo 模式:PHAuthorizationStatus 增加新的枚举 PHAuthorizationStatusLimited
typedef NS_ENUM(NSInteger, PHAuthorizationStatus) {
    PHAuthorizationStatusNotDetermined = 0, // User has not yet made a choice with regards to this application
    PHAuthorizationStatusRestricted,        // This application is not authorized to access photo data.
                                            // The user cannot change this application’s status, possibly due to active restrictions
                                            //   such as parental controls being in place.
    PHAuthorizationStatusDenied,            // User has explicitly denied this application access to photos data.
    PHAuthorizationStatusAuthorized,        // User has authorized this application to access photos data.
    PHAuthorizationStatusLimited API_AVAILABLE(ios(14)), // User has authorized this application for limited photo library access. Add PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YES to the application's Info.plist to prevent the automatic alert to update the users limited library selection. Use -[PHPhotoLibrary(PhotosUISupport) presentLimitedLibraryPickerFromViewController:] from PhotosUI/PHPhotoLibrary+PhotosUISupport.h to manually present the limited library picker.      
  • 相册访问方式: PHAccessLevel (请求查询limited权限在 accessLevel 为 readAndWrite 时生效)
typedef NS_ENUM(NSInteger, PHAccessLevel) {
    PHAccessLevelAddOnly = 1,
    PHAccessLevelReadWrite = 2,
} API_AVAILABLE(macos(11.0), ios(14), tvos(14));      
iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)

1.3 适配demo

要点:APP 需要监听用户修改了允许访问的资源并且更新资源列表

private

  1. 手动触发选择更多照片或取消选择以移除访问权限的界面:​

    ​ [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];​

  2. 显示允许访问的相册:​

    ​+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(PHFetchOptions *)options; ​

  3. 请求limited 权限:​

    ​requestAuthorizationForAccessLevel:handler:​

  4. 查询权限相册授权状态:​

    ​+ (PHAuthorizationStatus)authorizationStatusForAccessLevel:(PHAccessLevel)accessLevel API_AVAILABLE(macosx(11.0), ios(14), tvos(14)); ​

  5. 图片选择器:单选​

    ​UIImagePickerController​

  6. 图片选择器: 多选​

    ​PHPickerViewController​

II 具体适配方案

2.1 手动触发选择/取消选择图片以移除访问权限的界面

优化选择图片弹窗提示的交互方式:官方建议关闭自动弹窗,推荐在用户选择了 Limited Photo 模式时,在特定界面(例如设置页)显示修改资源的入口,当用户主动点击该入口时弹出系统弹窗。

  1. 关闭系统自动的选择弹窗提示:可以在 info.plist 中设置 PHPhotoLibraryPreventAutomaticLimitedAccessAlert 选项为 YES ,
<key>NSPhotoLibraryUsageDescription</key>
  <string>你可以分享相机胶卷中的照片、将照片保存</string>
  <key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
  <false/>      
  1. 手动触发选择更多照片或取消选择以移除访问权限的界面:
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];      
iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)

2.2 相册访问方式

还在使用 AssetsLibrary 管理资源读写的尽快迁移到 PhotoKit,对于只读,建议使用PHPicker。

3.2.1 只读权限

只读: 建议使用 iOS 14 提供的图片选择器 PHPicker 来选择图片资源(iOS 14 以下对应 UIImagePickerController)

PHPicker 优点:独立进程,不影响 APP 性能。不需要用户授予权限就可以选择用户所有的资源, 支持多选。

对应的代理协议如下

@interface ViewController () <UINavigationControllerDelegate, UIImagePickerControllerDelegate, PHPickerViewControllerDelegate>      

具体的实现代码请看第三章节。

3.2.2 只写权限

需要在 Info.plist 下配置权限信息

<key>NSPhotoLibraryAddUsageDescription</key>
    <string>你可以分享相机胶卷中的照片、将照片保存</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>你可以分享相机胶卷中的照片</string>
    <key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
    <false/>      

分别使用​

​ AssetsLibrary​

​​、​

​PhotoKit​

​​ 以及 UIKit 层 ​

​UIImageWriteToSavedPhotosAlbum​

​ 写入相册

- (IBAction)saveClick:(UIButton *)sender {
    //参数1:图片对象
    //参数2:成功方法绑定的target
    //参数3:成功后调用方法
    //参数4:需要传递信息(成功后调用方法的参数) 一般写nil
    UIImageWriteToSavedPhotosAlbum(self.imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

    /* 1.先保存图片到【相机胶卷】(不能直接保存到自定义相册中)
        1> C语言函数
        2> AssetsLibrary框架  (iOS4支持,iOS9.0被废弃)
        3> Photos框架 (iOS 8.0支持 推荐使用)
       2.拥有一个【自定义相册】
        1> AssetsLibrary框架
        2> Photos框架
       3.将刚才保存到【相机胶卷】里面的图片引用到【自定义相册】
        1> AssetsLibrary框架
        2> Photos框架
     */

}
#pragma mark -- <保存到相册>
-(void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    NSString *msg = nil ;
    if(error){
        msg = @"保存图片失败" ;
    }else{
        msg = @"保存图片成功"      
iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)

3.2.3 读写权限

分别使用 AssetsLibrary 和 PhotoKit 来读取相册资源

2.3 其他需要注意的API

2.4 监听第一次相册授权时

  1. 监听到用户点击不允许,不允许时显示引导。
  2. 用户未作出明确选择的情况下自己主动请求了一次权限设置(刷新UI的代码放到主线程执行)
/**
 1. 监听到用户点击不允许,不允许时显示引导
 2. 用户未作出明确选择的情况下自己主动请求了一次权限设置
 showAlert:不允许时显示引导
 block: 允许之后的动作,比如保存图片
 */
+(BOOL)isHasPhotoLibraryAuthorityWithisShowAlert:(BOOL)showAlert  block:(void (^)(id sender))block
{
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus] ;
//1.  定义局部block:  处理没有权限的情况,显示引导
    BOOL (^block4none)(PHAuthorizationStatus ) = ^ BOOL (PHAuthorizationStatus status ){
        NSLog(@" 没有访问图库的权限==============");
        if (showAlert) {
            [LBAlertController showAlertTitle:@"无法使用相册" content:@"请在iPhone的\"设置-隐私-照片\"中允许访问照片。" cancelString:@"取消" cancleBlock:nil sureString:@"去设置" sureBlock:^{
                // 需要在info.plist中添加 URL types 并设置一项URL Schemes为prefs  IOS10 以后不起作用
                    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]){
                    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
                }
            } currentController:[QCT_Common getCurrentVC]];
        }


        return NO;

    };
    switch (status) {
        case PHAuthorizationStatusRestricted:
        case PHAuthorizationStatusDenied : {

//没有访问图库的权限
            return block4none(status);


        }
            break;

        case PHAuthorizationStatusNotDetermined:{//2. 用户未作出明确选择的情况下自己主动请求了一次权限设置
            [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus phStatus) {
                //监听到用户点击不允许,不允许时显示引导
                if (phStatus == PHAuthorizationStatusRestricted || phStatus == PHAuthorizationStatusDenied) {

                    dispatch_sync(dispatch_get_main_queue(), ^{
                        //刷新UI的代码放到主线程执行
                        block4none(status);

                    });



                } else if (phStatus == PHAuthorizationStatusNotDetermined) {

                    // 不处理
                } else {
                    // 执行外围的block
//                    status = QMUIAssetAuthorizationStatusAuthorized;

                    if(block){//执行允许之后的保存图片操作
                        block(nil);
                    }

                }

            }];

            return  NO;

        }

        default:
            break;
    }
    if(block){// 3. 执行允许之后的保存图片操作
        block(nil);
    }
    return  YES;
}      

III 选择图片资源视图

3.1 请求查询权限

/**
 请求查询权限
 */
- (IBAction)requestAuth:(id)sender
{

    // 请求权限,需注意 limited 权限尽在 accessLevel 为 readAndWrite 时生效
    PHAccessLevel level1 = PHAccessLevelAddOnly;// 仅允许添加照片
    PHAccessLevel level2 = PHAccessLevelReadWrite;// 允许访问照片,limitedLevel 必须为 readWrite
    [PHPhotoLibrary requestAuthorizationForAccessLevel:level2 handler:^(PHAuthorizationStatus status) {
      switch (status) {
      /**
        请求查询限制权限:需注意 `PHAuthorizationStatusLimited` 权限在 accessLevel 为 `PHAccessLevelReadWrite` 时生效
      */
          case PHAuthorizationStatusLimited:
              NSLog(@"limited");
              break;
          case PHAuthorizationStatusDenied:
              NSLog(@"denied");
              break;
          case PHAuthorizationStatusAuthorized:
              NSLog(@"authorized");
              break;
          default:
              break;
      }
    }];
}      

3.2 使用UIImagePickerController选择图片资源(单选)

  • 初始化
#pragma

/**
 UIImagePickerController
 */
- (UIImagePickerController *)picker
{
    if (!_picker) {
        _picker = [[UIImagePickerController alloc]init];
    }
    return _picker;
}


- (IBAction)openPickerAciton:(id)sender
{
    self.isDoing = NO;

    if (self.isDoing) {
        return;
    }

    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] == NO) {
        return;
    }
    self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    self.isDoing = YES;
    [self presentViewController:self.picker animated:YES completion:nil];
}      
  • 处理代理
void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    self.imageView.image = image;

    [picker dismissViewControllerAnimated:YES completion:nil];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:nil];
}      

3.3 使用PHPicker选择图片资源(多选)

  • 初始化
#pragma


- (IBAction)openNewPicker:(id)sender
{
    //三种过滤类型
    PHPickerFilter *imagesFilter = PHPickerFilter.imagesFilter;
    PHPickerFilter *videosFilter = PHPickerFilter.videosFilter;
    PHPickerFilter *livePhotosFilter = PHPickerFilter.livePhotosFilter;

    PHPickerConfiguration *configuration = [[PHPickerConfiguration alloc] init];
    configuration.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[imagesFilter]]; // 可配置查询用户相册中文件的类型,支持三种
    configuration.selectionLimit = 0; // 默认为1,为0为跟随系统上限

    PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:configuration];
    picker.delegate = self;
    [self presentViewController:picker animated:YES completion:nil];
}      
  • 处理代理
void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14)) {
    [picker dismissViewControllerAnimated:YES completion:nil];
     if (!results || !results.count) {
         return;
     }
     NSItemProvider *itemProvider = results.firstObject.itemProvider;
     if ([itemProvider canLoadObjectOfClass:UIImage.class]) {
         __weak typeof(self) weakSelf = self;
         //异步获取
         [itemProvider loadObjectOfClass:UIImage.class completionHandler:^(__kindof id<NSItemProviderReading>  _Nullable object, NSError * _Nullable error) {
             if ([object isKindOfClass:UIImage.class]) {
                 __strong typeof(self) strongSelf = weakSelf;
                 dispatch_async(dispatch_get_main_queue(), ^{
                     strongSelf.imageView.image = (UIImage      

see also