在刚刚结束的线上 WWDC 2020 发布会上苹果向我们展示了新的 iOS14 系统。iOS14 的适配,很重要的一环就集中在用户隐私和安全方面。
在 iOS13 及以前,当用户首次访问应用程序时,会被要求开放大量权限,比如相册、定位、联系人,实际上该应用可能仅仅需要一个选择图片功能,却被要求开放整个照片库的权限,这确实是不合理的。对于相册,在 iOS14 中引入了 “LimitedPhotos Library” 的概念,用户可以授予应用访问其一部分的照片,对于应用来说,仅能读取到用户选择让应用来读取的照片,让我们看到了 Apple 对于用户隐私的尊重。这仅仅是一部分,在iOS14 中,可以看到诸多类似的保护用户隐私的措施,也需要我们升级适配。
最近在调研 iOS14的适配方案,本文主要分享一下 iOS14 上对于隐私授权的变更和部分适配方案,欢迎补充指正。
适配点
✎ iOS14 新增了“Limited Photo Library Access” 模式,在授权弹窗中增加了 Select Photo 选项。用户可以在 App 请求调用相册时选择部分照片让 App 读取。从 App 的视⻆来看,你的相册里就只有这几张照片,App 无法得知其它照片的存在。
✎ iOS14 中当用户选择
“PHAuthorizationStatusLimited” 时,如果未进行适配,有可能会在每次触发相册功能时都进行弹窗询问用户是否需要修改照片权限。
✎ 对于这种情况可通过在 Info.plist 中设置
“PHPhotoLibraryPreventAutomaticLimitedAccessAlert”的值为 YES 来阻止该弹窗反复弹出,并且可通过下面这个 API 来主动控制何时弹出PHPickerViewController 进行照片选择。
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];
✎ 在 iOS14 中官方推荐使用 PHPicker 来替代原 API 进行图片选择。PHPicker 为独立进程,会在视图最顶层进行展示,应用内无法对其进行截图也无法直接访问到其内的数据。
UIImagePickerController -> PHPickerViewController, UIImagePickerViewController 功能受限,每次只能选择一张图片,将逐渐被废弃。
PHPicker 支持多选,支持搜索,支持按 image,video,livePhotos 等进行选择。
@interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView;@property (nonatomic, strong) NSArray *itemProviders; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view.} - (IBAction)button:(id)sender { // 以下 API 仅为 iOS14 only PHPickerConfiguration *configuration = [[PHPickerConfiguration alloc] init]; configuration.filter = [PHPickerFilter videosFilter]; // 可配置查询用户相册中文件的类型,支持三种 configuration.selectionLimit = 0; // 默认为1,为0时表示可多选。 PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:configuration]; picker.delegate = self; // picker vc,在选完图片后需要在回调中手动 dismiss [self presentViewController:picker animated:YES completion:^{ }]; } #pragma mark - Delegate - (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results { [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 _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 *)object; }); } }]; } }
typedef NS_ENUM(NSInteger, PHAccessLevel) { PHAccessLevelAddOnly = 1, // 仅允许添加照片 PHAccessLevelReadWrite = 2, // 允许访问照片,limitedLevel 必须为 readWrite} API_AVAILABLE(macos(10.16), ios(14), tvos(14));
// 查询权限PHAccessLevel level = PHAccessLevelReadWrite;PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:level]; switch (status) { case PHAuthorizationStatusLimited: NSLog(@"limited"); break; case PHAuthorizationStatusDenied: NSLog(@"denied"); break; case PHAuthorizationStatusAuthorized: NSLog(@"authorized"); break; default: break;}
// 请求权限,需注意 limited 权限尽在 accessLevel 为 readAndWrite 时生效[PHPhotoLibrary requestAuthorizationForAccessLevel:level handler:^(PHAuthorizationStatus status) { switch (status) { case PHAuthorizationStatusLimited: NSLog(@"limited"); break; case PHAuthorizationStatusDenied: NSLog(@"denied"); break; case PHAuthorizationStatusAuthorized: NSLog(@"authorized"); break; default: break; }}];
NSSet *patterns = [[NSSet alloc] initWithObjects:UIPasteboardDetectionPatternProbableWebURL, nil];[[UIPasteboard generalPasteboard] detectPatternsForPatterns:patterns completionHandler:^(NSSet * _Nullable result, NSError * _Nullable error) { if (result && result.count) { // 当前剪切板中存在 URL }}];
AVAudioRecorder *recorder = [[AVAudioRecorder alloc] initWithURL:recorderPath settings:nil error:nil];[recorder record];
AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:nil];AVCaptureSession *session = [[AVCaptureSession alloc] init];if ([session canAddInput:videoInput]) { [session addInput:videoInput];}[session startRunning];
if ([[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]) { NSString *idfaString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString; NSLog(@"%@", idfaString);}
#import #import
- (void)testIDFA { if (@available(iOS 14, *)) { [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { if (status == ATTrackingManagerAuthorizationStatusAuthorized) { NSString *idfaString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString; } }]; } else { // 使用原方式访问 IDFA }}
对于用户拒绝授权 UserTracking 的情况,可以考虑接入苹果的 SKAdNetwork 框架进行广告分析。感兴趣的同学可进一步了解:https://developer.apple.com/documentation/storekit/skadnetwork
下一期,我们聊聊 【Metal 新特性详解|带来的技术启发和思考】,敬请持续关注~
WWDC 2020 Apple Developer
Developer Documentation
手淘客户端—小程序与跨平台技术部
我们是支撑小程序、小游戏、Flutter 等跨平台技术的核心团队,有技术广度和也有技术深度,我们需要 iOS、Android、C++、Flutter、Canvas、游戏引擎、WebGL 等各方面的人才。如果你善于学习,这是一个很好的接触跨领域知识的机会。
如果你是个对技术有追求对小伙伴,请别犹豫,立刻联系我!
????:[email protected]
✿ 拓展阅读
作者|盛兰雅(岚遥)
编辑|橙子君
出品|阿里巴巴新零售淘系技术