一、权限设置

iOS 10以后,苹果增强了对用户隐私的保护,访问照相机、相册、麦克风、定位、通讯录等,都需要在info.plist 文件中添加权限,而且少一个都不行。根据需要添加,项目中使用了什么,就添加对应的权限。

权限字段
相册使用权限Privacy - Photo Library Usage Description
编辑相册权限Privacy - Photo Library Additions Usage Description
相机权限Privacy - Camera Usage Description
通讯录权限Privacy - Contacts Usage Description
麦克风权限Privacy - Microphone Usage Description
蓝牙权限Privacy - Bluetooth Peripheral Usage Description
定位权限Privacy - Location Always Usage Description
定位权限Privacy - Location Usage Description
定位权限Privacy - Location When In Use Usage Description
定位权限Privacy - Location Always and When In Use Usage Description
媒体库权限Privacy - Media Library Usage Description
日历权限Privacy - Calendars Usage Description
提醒事项权限Privacy - Reminders Usage Description
FaceID权限Privacy - Face ID Usage Description
健康记录权限Privacy - Health Records Usage Description
健康分享权限Privacy - Health Share Usage Description
健康更新权限Privacy - Health Update Usage Description
运动权限Privacy - Motion Usage Description
音乐权限Privacy - Music Usage Description
NFC权限Privacy - NFC Scan Usage Description
Siri权限Privacy - Siri Usage Description
语音识别权限Privacy - Speech Recognition Usage Description
智能家具权限Privacy - HomeKit Usage Description
电视供应商权限Privacy - TV Provider Usage Description
视频用户账号使用权限Privacy - Video Subscriber Account Usage Description

事例:

Privacy - Camera Usage Description
APP需要选择视频进行编辑,请允许访问您的相机

Privacy - Microphone Usage Description
APP需要选择视频进行编辑,请允许访问您的麦克风

Privacy - Photo Library Additions Usage Description
APP需要导出视频到相册,请允许访问您的相册

二、权限判断

在 APP 的开发中,有很多功能是需要用户授权了之后才能使用,这里将常用的权限判断做一下总结。

2.1 网络权限
@import CoreTelephony; //引入头文件 

//应用启动后,检测应用中是否有联网权限
CTCellularData *cellularData = [[CTCellularData alloc]init];
cellularData.cellularDataRestrictionDidUpdateNotifier =  ^(CTCellularDataRestrictedState state){
  //获取联网状态
  switch (state) {
      case kCTCellularDataRestricted:
          NSLog(@"Restricrted");
          break;
      case kCTCellularDataNotRestricted:
          NSLog(@"Not Restricted");
          break;
      case kCTCellularDataRestrictedStateUnknown:
          NSLog(@"Unknown");
          break;
      default:
          break;
  };
};

//查询应用是否有联网功能
CTCellularData *cellularData = [[CTCellularData alloc]init];
CTCellularDataRestrictedState state = cellularData.restrictedState;
 switch (state) {
  case kCTCellularDataRestricted:
      NSLog(@"Restricrted");
      break;
  case kCTCellularDataNotRestricted:
      NSLog(@"Not Restricted");
      break;
  case kCTCellularDataRestrictedStateUnknown:
      NSLog(@"Unknown");
      break;
  default:
      break;
}
2.2 相册权限( iOS 8.0以上)
@import Photos; 导入头文件

//检查是否有相册权限
PHAuthorizationStatus photoAuthorStatus = [PHPhotoLibrary authorizationStatus];
switch (photoAuthorStatus) {
  case PHAuthorizationStatusAuthorized:
      NSLog(@"Authorized");
      break;
  case PHAuthorizationStatusDenied:
      NSLog(@"Denied");
      break;
  case PHAuthorizationStatusNotDetermined:
      NSLog(@"not Determined");
      break;
  case PHAuthorizationStatusRestricted:
      NSLog(@"Restricted");
      break;
  default:
      break;
}

//获取相册权限
 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
  if (status == PHAuthorizationStatusAuthorized) {
      NSLog(@"Authorized");
  }else{
      NSLog(@"Denied or Restricted");
  }
  }];
2.3 相机和麦克风权限
@import AVFoundation;  //导入头文件

//检查是否有相机或麦克风权限
//相机权限
AVAuthorizationStatus AVstatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
//麦克风权限
AVAuthorizationStatus AVstatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
switch (AVstatus) {
  case AVAuthorizationStatusAuthorized:
      NSLog(@"Authorized");
      break;
  case AVAuthorizationStatusDenied:
      NSLog(@"Denied");
      break;
  case AVAuthorizationStatusNotDetermined:
      NSLog(@"not Determined");
      break;
  case AVAuthorizationStatusRestricted:
      NSLog(@"Restricted");
      break;
  default:
      break;
}

//获取相机或麦克风权限
//相机权限
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
  if (granted) {
      NSLog(@"Authorized");
  }else{
      NSLog(@"Denied or Restricted");
  }
}];

//麦克风权限
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
  if (granted) {
      NSLog(@"Authorized");
  }else{
      NSLog(@"Denied or Restricted");
  }
}];
2.4 定位权限
@import CoreLocation;   //导入头文件

//检查定位功能是否可用
 BOOL isLocation = [CLLocationManager locationServicesEnabled];
if (!isLocation) {
  NSLog(@"not turn on the location");
}

//检查定位权限
CLAuthorizationStatus CLstatus = [CLLocationManager authorizationStatus];
switch (CLstatus) {
  case kCLAuthorizationStatusAuthorizedAlways:
      NSLog(@"Always Authorized");
      break;
  case kCLAuthorizationStatusAuthorizedWhenInUse:
      NSLog(@"AuthorizedWhenInUse");
      break;
  case kCLAuthorizationStatusDenied:
      NSLog(@"Denied");
      break;
  case kCLAuthorizationStatusNotDetermined:
      NSLog(@"not Determined");
      break;
  case kCLAuthorizationStatusRestricted:
      NSLog(@"Restricted");
      break;
  default:
      break;
}
获取定位权限

CLLocationManager *manager = [[CLLocationManager alloc] init];
//一直获取定位信息
[manager requestAlwaysAuthorization];
//使用的时候获取定位信息
[manager requestWhenInUseAuthorization];


//在代理方法中查看权限是否改变
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
 switch (status) {
  case kCLAuthorizationStatusAuthorizedAlways:
      NSLog(@"Always Authorized");
      break;
  case kCLAuthorizationStatusAuthorizedWhenInUse:
      NSLog(@"AuthorizedWhenInUse");
      break;
  case kCLAuthorizationStatusDenied:
      NSLog(@"Denied");
      break;
  case kCLAuthorizationStatusNotDetermined:
      NSLog(@"not Determined");
      break;
  case kCLAuthorizationStatusRestricted:
      NSLog(@"Restricted");
      break;
  default:
      break;
  }

}
2.5 推送权限
//检查是否有推送权限
UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings];
switch (settings.types) {
  case UIUserNotificationTypeNone:
      NSLog(@"None");
      break;
  case UIUserNotificationTypeAlert:
      NSLog(@"Alert Notification");
      break;
  case UIUserNotificationTypeBadge:
      NSLog(@"Badge Notification");
      break;
  case UIUserNotificationTypeSound:
      NSLog(@"sound Notification'");
      break;

  default:
      break;
}

//获取推送权限
UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:setting];
2.6 通讯录权限 (iOS9.0及以后)
@import Contacts;  //导入头文件

//检查是否有通讯录权限
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
  switch (status) {
        case CNAuthorizationStatusAuthorized:
        {
            NSLog(@"Authorized:");
        }
            break;
        case CNAuthorizationStatusDenied:{
            NSLog(@"Denied");
        }
            break;
        case CNAuthorizationStatusRestricted:{
            NSLog(@"Restricted");
        }
            break;
        case CNAuthorizationStatusNotDetermined:{
             NSLog(@"NotDetermined");
        }
            break;
}

//获取通讯录权限
CNContactStore *contactStore = [[CNContactStore alloc] init];
[contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted) {
    
       NSLog(@"Authorized");
    
    }else{
    
       NSLog(@"Denied or Restricted");
    }
 }];

2.7 日历、备忘录权限
@import EventKit; //导入头文件

typedef NS_ENUM(NSUInteger, EKEntityType) {
  EKEntityTypeEvent,  //日历
  EKEntityTypeReminder   //备忘
 };
 
//检查是否有日历或者备忘录权限
EKAuthorizationStatus EKstatus = [EKEventStore  authorizationStatusForEntityType:EKEntityTypeEvent];
switch (EKstatus) {
  case EKAuthorizationStatusAuthorized:
      NSLog(@"Authorized");
      break;
  case EKAuthorizationStatusDenied:
      NSLog(@"Denied'");
      break;
  case EKAuthorizationStatusNotDetermined:
      NSLog(@"not Determined");
      break;
  case EKAuthorizationStatusRestricted:
      NSLog(@"Restricted");
      break;
  default:
      break;
}

//获取日历或备忘录权限
EKEventStore *store = [[EKEventStore alloc]init];
[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) {
  if (granted) {
      NSLog(@"Authorized");
  }else{
      NSLog(@"Denied or Restricted");
  }
}];

三、跳转到权限设置页面

在 APP 开发中,经常需要使用到一些权限,比如相机、定位、通知等,这些权限都需要用户授权之后,才能进行使用。一般的,当系统第一次需要用到这些功能时,系统会主动弹出授权提示框,询问用户是否同意 APP 使用该功能。如果用户选择了拒绝授权,那么用户就不能使用改功能。那么在用户拒绝授权之后,如何重新去授权使用该功能呢?通常有两种做法:

  1. 不做跳转,只弹出提示框,告知用户去 “设置 > 隐私 > 相机” 开启使用相机功能;
  2. 弹框提示,并跳转到设置的子界面;

方案一是苹果推荐的方法,因为苹果不建议开发者直接跳转到设置的子界面,并且苹果将跳转到设置子界面的一些 API 改成了私有的 API,所以方案二其实上架是有被拒的风险,但是从用户体验来讲,方案二显然更友好,所以开发者自己决定用哪种方案去实现吧,下面直接介绍方案二的实现代码。

// iOS 8.1 ~ iOS 14
- (void)pushSettingController {
    NSURL *url1 = [NSURL URLWithString:@"App-Prefs:root=NOTIFICATIONS_ID"];
    NSURL *url2 = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    if (@available(iOS 11.0, *)) {
        if ([[UIApplication sharedApplication] canOpenURL:url2]){
            [[UIApplication sharedApplication] openURL:url2 options:@{} completionHandler:nil];
        }
    } else {
        if ([[UIApplication sharedApplication] canOpenURL:url1]){
            if (@available(iOS 10.0, *)) {
                [[UIApplication sharedApplication] openURL:url1 options:@{} completionHandler:nil];
            } else {
                [[UIApplication sharedApplication] openURL:url1];
            }
        }
    }
}

附:设置页面对应的字符串

页面名称对应字符串
照片与相机App-Prefs:root=Photos
隐私->麦克风App-Prefs:root=Privacy&path=MICROPHONE
隐私->定位App-Prefs:root=Privacy&path=LOCATION
隐私->相机App-Prefs:root=Privacy&path=CAMERA
个人热点App-Prefs:root=INTERNET_TETHERING
运营商App-Prefs:root=Carrier
无线局域网App-Prefs:root=WIFI
蜂窝移动网络App-Prefs:root=MOBILE_DATA_SETTINGS_ID
蓝牙App-Prefs:root=Bluetooth
通知App-Prefs:root=NOTIFICATIONS_ID
页面名称对应字符串
通用App-Prefs:root=General
通用-关于本机App-Prefs:root=General&path=About
通用-键盘App-Prefs:root=General&path=Keyboard
通用-辅助功能App-Prefs:root=General&path=ACCESSIBILITY
通用-语言与地区App-Prefs:root=General&path=INTERNATIONAL
通用-还原App-Prefs:root=Reset
墙纸App-Prefs:root=Wallpaper
SiriApp-Prefs:root=SIRI
隐私App-Prefs:root=Privacy
SafariApp-Prefs:root=SAFARI
音乐App-Prefs:root=MUSIC
音乐-均衡器App-Prefs:root=MUSIC&path=com.apple.Music:EQ
FaceTimeApp-Prefs:root=FACETIME

四、打开任意APP设置页面

采用逆向开发的手段,通过Hook可以实现跳转到任意APP的设置页面。

#import <Foundation/Foundation.h>

@interface LSApplicationWorkspaceHook : NSObject

+ (instancetype)defaultWorkspace;
- (void)openURL:(NSURL *)url;
- (void)openWeChat;

@end
#import "LSApplicationWorkspaceHook.h"

@implementation LSApplicationWorkspaceHook

-(void)openWeChat {
    // 调用 LSApplicationWorkspace 的单例方法
    Class aClass = NSClassFromString(@"LSApplicationWorkspace");
    LSApplicationWorkspaceHook *hook = [aClass defaultWorkspace];
    // 调用 LSApplicationWorkspace 的 `openURL:` 方法
    [hook openURL:[NSURL URLWithString:@"app-prefs:com.tencent.xin"]];
}

@end

调用打开微信设置页面:

[[LSApplicationWorkspaceHook alloc] openWeChat];
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐