前言

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

用户隐私已经不断被资产化,通过商业运作成为了一种牟利的手段。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)_ios

I iOS14相册权限适配

预备知识: 冷启动: App 不在内存中/没有相关的进程存在 热启动:(从后台恢复)内存有部分,无相关的进程存在 暂停:存在内存中,存在相关进程

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

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

当页面弹出请求权限 Alert 时,会有​​选择照片...​​选项,用户选择该选项时,会弹出页面供用户选择允许App访问的照片。

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

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

  1. 手动触发选择/取消选择图片以移除访问权限的界面 :​​ [[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];​

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

  1. 在应用设置->Photos->Edit Selected Photos中修改

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

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

iOS小技能:iOS14相册权限适配 (Limited Photo Library Access)_保存图片_05

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)_ios_06

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)_ios_07

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)_iOS_08

3.2.3 读写权限

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

2.3 其他需要注意的API

使用 PHAssetCreationRequests 来创建的资源默认是会添加在用户允许的集合当中

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 选择图片资源视图

使用PHPicker和UIImagePickerController选择图片资源

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

公号:iOS逆向