一、前言
大數(shù)據(jù)時(shí)代,用戶對自己的隱私安全越來越關(guān)注,所以,隨著iOS系統(tǒng)更新,蘋果對用戶隱私相關(guān)(定位、相冊、網(wǎng)絡(luò)、粘貼板等)權(quán)限控制持續(xù)升級。其中定位權(quán)限相關(guān)申請API與配置項(xiàng)較多,本文旨在記錄說明 iOS8.0 - 14.0beta 從權(quán)限申請到獲取定位數(shù)據(jù)流程。
內(nèi)容包括定位權(quán)限、獲取定位數(shù)據(jù)、定位權(quán)限API調(diào)用實(shí)踐。定位權(quán)限模塊按照系統(tǒng)相關(guān)性分別介紹該系統(tǒng)下權(quán)限配置與API調(diào)用細(xì)節(jié)、注意事項(xiàng)與表格總結(jié);獲取定位數(shù)據(jù)模塊介紹定位關(guān)鍵參數(shù)、單次/連續(xù)定位等;調(diào)用實(shí)踐模塊介紹了從 iOS8.0 - 14.0beta 系統(tǒng)定位權(quán)限的適配實(shí)踐。
二、定位權(quán)限
1、iOS8.*
-前臺定位
-需要在info.plist配置NSLocationWhenInUseUsageDescription字段;
-首次使用定位時(shí),通過API接口requestWhenInUseAuthorization申請應(yīng)用使用時(shí)權(quán)限;
注意:此權(quán)限下,如果Xcode勾選 Capabilities -> UIBackgroundModes > Location updates,則app退到后臺仍可獲取定位數(shù)據(jù),但此時(shí)在手機(jī)上方會有定位小藍(lán)條提示;
-后臺定位
-需要在info.plist配置NSLocationAlwaysUsageDescription字段;
-需要Xcode勾選 Capabilities -> UIBackgroundModes > Location updates;
-首次使用定位時(shí),通過API接口requestAlwaysAuthorization申請應(yīng)用未使用時(shí)權(quán)限;
2、iOS9.與iOS10.
-****前臺定位****
-需要在info.plist配置NSLocationWhenInUseUsageDescription字段;
-首次使用定位時(shí),通過API接口requestWhenInUseAuthorization申請應(yīng)用使用時(shí)權(quán)限,如下圖;

注意:此權(quán)限下,如果Xcode勾選 Capabilities -> UIBackgroundModes > Location updates并且allowsBackgroundLocationUpdates設(shè)為YES,則app退到后臺仍可獲取定位數(shù)據(jù),但此時(shí)在手機(jī)上方會有定位小藍(lán)條提示;
-****后臺定位****
-需要在info.plist配置NSLocationAlwaysUsageDescription字段;
-需要Xcode勾選 Capabilities -> UIBackgroundModes > Location updates;
-需要CLLocationManager設(shè)置allowsBackgroundLocationUpdates為YES;
-首次使用定位時(shí),通過API接口requestAlwaysAuthorization申請應(yīng)用未使用時(shí)權(quán)限,如下圖;

-與iOS8.版本相比不同點(diǎn)***
-iOS9.后臺增加了allowsBackgroundLocationUpdates屬性,可以認(rèn)為在iOS8.下allowsBackgroundLocationUpdates永遠(yuǎn)為YES;
3、iOS11.與iOS12.
-****前臺定位****
-需要在info.plist配置NSLocationWhenInUseUsageDescription字段;
-首次使用定位時(shí),通過API接口requestWhenInUseAuthorization申請應(yīng)用使用時(shí)權(quán)限,如下圖;

注意:此權(quán)限下,如果Xcode勾選 Capabilities -> UIBackgroundModes > Location updates并且allowsBackgroundLocationUpdates設(shè)為YES,則app退到后臺仍可獲取定位數(shù)據(jù),但此時(shí)在手機(jī)上方會有定位小藍(lán)條提示,此小藍(lán)條不可隱藏;
-****后臺定位****
-需要在info.plist配置NSLocationWhenInUseUsageDescription、NSLocationAlwaysAndWhenInUseUsageDescription字段;
-需要Xcode勾選 Capabilities -> UIBackgroundModes > Location updates;
-需要CLLocationManager設(shè)置allowsBackgroundLocationUpdates為YES;
-首次使用定位時(shí),通過API接口requestAlwaysAuthorization申請應(yīng)用未使用時(shí)權(quán)限,如下圖;

注意:此權(quán)限下,當(dāng)app在后臺時(shí),系統(tǒng)默認(rèn)不展示定位小藍(lán)條,可通過showsBackgroundLocationIndicator控制小藍(lán)條是否顯示;
-與iOS10.版本相比不同點(diǎn)***
-iOS11.*變更了后臺定位權(quán)限配置字段;
-iOS11.*以后如果申請后臺定位,info.plist需要同時(shí)配置NSLocationWhenInUseUsageDescription、NSLocationAlwaysAndWhenInUseUsageDescription兩個(gè)字段;
-iOS11.增加了showsBackgroundLocationIndicator屬性,當(dāng)擁有后臺定位權(quán)限時(shí),用于控制定位小藍(lán)條是否顯示。可以認(rèn)為在iOS10.之前showsBackgroundLocationIndicator永遠(yuǎn)為NO;
4、iOS13.*
-****前臺定位****
-需要在info.plist配置NSLocationWhenInUseUsageDescription字段;
-首次使用定位時(shí),通過API接口requestWhenInUseAuthorization申請應(yīng)用使用時(shí)權(quán)限;
注意:權(quán)限申請彈窗與之前版本不一致,新增了允許一次選項(xiàng);如果用戶選擇允許一次后,下次在使用app時(shí),仍可重新調(diào)用API申請定位權(quán)限;如下圖

-****后臺定位****
-需要在info.plist配置NSLocationWhenInUseUsageDescription、NSLocationAlwaysAndWhenInUseUsageDescription字段;
-需要Xcode勾選 Capabilities -> UIBackgroundModes > Location updates;
-需要CLLocationManager設(shè)置allowsBackgroundLocationUpdates為YES;
-首次使用定位時(shí),通過API接口requestAlwaysAuthorization申請權(quán)限;
注意:1)直接調(diào)用requestAlwaysAuthorization申請權(quán)限時(shí),權(quán)限彈窗與調(diào)用requestWhenInUseAuthorization一樣,如上圖,用戶只可以選擇應(yīng)用使用時(shí)或者只允許一次。不同點(diǎn):當(dāng)選擇使用app時(shí)允許選項(xiàng)后,狀態(tài)變更的回調(diào)為kCLAuthorizationStatusAuthorizedAlways;并且當(dāng)app退到后臺后,系統(tǒng)會擇機(jī)彈窗提示用戶是否要升級權(quán)限為始終允許。如下圖:

2)如果想要在應(yīng)用使用期間彈窗申請始終允許,則需要先調(diào)用requestWhenInUseAuthorization,并且獲得應(yīng)用使用期間定位權(quán)限,之后在調(diào)用requestAlwaysAuthorization則可彈窗申請始終允許,如下圖;(感覺不是太友好,不建議使用)

-與iOS12.版本相比不同點(diǎn)***
-使用應(yīng)用期間的定位權(quán)限增加了允許一次選項(xiàng);
-不能直接申請后臺定位權(quán)限,需要用戶先選擇應(yīng)用使用期間的定位權(quán)限后,在進(jìn)行權(quán)限升級;
5、iOS14.*(beta版本)
-****前臺定位****
-需要在info.plist配置NSLocationWhenInUseUsageDescription字段;
-首次使用定位時(shí),通過API接口requestWhenInUseAuthorization申請應(yīng)用使用時(shí)權(quán)限;
注意:權(quán)限申請彈窗與之前版本不一致,新增了精確位置開關(guān),新增了小地圖展示當(dāng)前位置;小地圖的顯示,支持在手機(jī)定位設(shè)置中選擇,如果選擇關(guān)閉不顯示則手機(jī)中所有app都不顯示此小地圖。如下圖

-****后臺定位****
-需要在info.plist配置NSLocationWhenInUseUsageDescription、NSLocationAlwaysAndWhenInUseUsageDescription字段;
-需要Xcode勾選 Capabilities -> UIBackgroundModes > Location updates;
-需要CLLocationManager設(shè)置allowsBackgroundLocationUpdates為YES;
-首次使用定位時(shí),通過API接口requestAlwaysAuthorization申請權(quán)限;
-****新增精度權(quán)限****
-需要在info.plist配置NSLocationTemporaryUsageDescriptionDictionary,如下:
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
ExampleUsageDescription
This app needs accurate location so it can verify that you are in a supported region.
AnotherUsageDescription
This app needs accurate location so it can show you relevant results.
</dict>
-新增屬性字段@property (nonatomic, readonly) CLAccuracyAuthorization accuracyAuthorization API_AVAILABLE(ios(14.0), macos(11.0), watchos(7.0), tvos(14.0));可以獲取當(dāng)前的定位精度權(quán)限。
-在app已經(jīng)獲得定位權(quán)限之后,并且當(dāng)前用戶選擇的是模糊定位,則允許應(yīng)用申請一次臨時(shí)精確定位權(quán)限,申請api為- (void)requestTemporaryFullAccuracyAuthorizationWithPurposeKey:(NSString )purposeKey completion:(void(^)(NSError ))completion; 其中purposeKey既為plist中配置字典中的key,可以有多個(gè),對應(yīng)app中不同的定位需求場景;注意:**此API不能用于申請定位權(quán)限,只能用于從模糊定位升級為精確定位;申請定位權(quán)限只能調(diào)用requestWhen或requestAlways,如果沒有獲得定位權(quán)限,直接調(diào)用此API無效。如下圖

-如果app默認(rèn)不使用精確定位,則可以在info.plist中配置NSLocationDefaultAccuracyReduced字段,配置該字段后,申請定位權(quán)限的小地圖中不在有精確定位的開關(guān),即為關(guān)。如下面圖示
-需要注意該字段類型為Boolean,如果為其他類型則不起效;
-配置該字段后,申請定位權(quán)限的小地圖左上角則沒有精確開關(guān),默認(rèn)關(guān)閉,如下面圖示。但是如果info.plist中配置了NSLocationTemporaryUsageDescriptionDictionary,則仍可以申請臨時(shí)的精確定位權(quán)限;
-??:測試期間使用Xcode12 beta1到beta4,直接使用info.plist的Property List添加NSLocationDefaultAccuracyReduced字段只能是string,所以會造成不起效的問題,如果您也遇到類似問題,可以點(diǎn)擊info.plist右鍵Open As -> Source Code,即使用源碼直接添加既可起效;
<key>NSLocationDefaultAccuracyReduced</key>
<true/>
[圖片上傳失敗...(image-24baae-1637218463749)]
-與iOS13.版本相比不同點(diǎn)***
-權(quán)限申請彈窗與之前版本不一致;
-新增精度權(quán)限相關(guān)plist設(shè)置、授權(quán)、讀??;
-新增臨時(shí)一次從模糊定位升級精確定位API;
-新增定位權(quán)限變更回調(diào);
6、定位權(quán)限更新回調(diào)
-****iOS13.*及以前:****- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status;
-如主動獲取定位權(quán)限可使用類方法:+ (CLAuthorizationStatus)authorizationStatus;
-CLAuthorizationStatus枚舉取值
typedef NS_ENUM(int, CLAuthorizationStatus) {
kCLAuthorizationStatusNotDetermined = 0, //用戶沒有決定是否使用定位服務(wù)
kCLAuthorizationStatusRestricted, //定位服務(wù)授權(quán)狀態(tài)受限制
kCLAuthorizationStatusDenied, //用戶拒絕/定位總開關(guān)關(guān)閉
kCLAuthorizationStatusAuthorizedAlways, //始終允許
kCLAuthorizationStatusAuthorizedWhenInUse, //在應(yīng)用使用期間
kCLAuthorizationStatusAuthorized //已經(jīng)廢棄,等同于始終允許
};
-iOS14.及以后***:- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager;
-通過manager.authorizationStatus對象方法獲取當(dāng)前定位權(quán)限,此方法在iOS13及以前版本是類方法;
-通過manager.accuracyAuthorization對象方法獲取當(dāng)前精度權(quán)限;
-CLAccuracyAuthorization枚舉取值
typedef NS_ENUM(NSInteger, CLAccuracyAuthorization) {
CLAccuracyAuthorizationFullAccuracy, //精確定位
CLAccuracyAuthorizationReducedAccuracy, //模糊定位
};
7、總結(jié)
-****定位權(quán)限注意事項(xiàng)****
-iOS11以后如果申請后臺定位,info.plist需要同時(shí)配置NSLocationWhenInUseUsageDescription、NSLocationAlwaysAndWhenInUseUsageDescription兩個(gè)字段;
-調(diào)用申請定位權(quán)限API,在用戶抉擇后,再次調(diào)用無效;
-如果調(diào)用requestWhenInUseAuthorization申請過使用期間的定位權(quán)限,并且得到用戶許可,則之后仍可調(diào)用requestAlwaysAuthorization申請一次后臺定位權(quán)限(即權(quán)限升級);
-iOS13后,直接調(diào)用requestAlwaysAuthorization申請權(quán)限時(shí),權(quán)限彈窗與調(diào)用requestWhenInUseAuthorization一樣,在app進(jìn)入后臺后,系統(tǒng)會擇機(jī)彈窗提示用戶是否要權(quán)限升級為始終允許;
-自2019年下半年起,蘋果商店上架app對后臺定位權(quán)限增加限制,如果info.plist中不包含NSLocationAlwaysUsageDescription/NSLocationAlwaysAndWhenInUseUsageDescription字段,則在app代碼中不能出現(xiàn)符號requestAlwaysAuthorization,否則上架審核不通過;
-****Info.plist 中的字段總結(jié)****
| iOS版本 | NSLocationWhenInUseUsageDescription | NSLocationAlwaysUsageDescription | NSLocationAlwaysAndWhenInUseUsageDescription | NSLocationTemporaryUsageDescriptionDictionary |
| :---: | :---: | :---: | :---: | :---: |
| iOS 8 | YES | YES | × | × |
| iOS 9 | YES | YES | × | × |
| iOS 10 | YES | YES | × | × |
| iOS 11 | YES | × | YES | × |
| iOS 12 | YES | × | YES | × |
| iOS 13 | YES | × | YES | × |
| iOS 14 | YES | × | YES | YES |
-****不同系統(tǒng)版本調(diào)用定位權(quán)限API差異****
****iOS8.0****
| - | Capabilities 關(guān) | Capabilities 開 |
| :--- | :---: | :---: |
| requestAlwaysAuthorization | 可以前臺定位、不可以后臺定位、無藍(lán)條 | 可以前臺定位、可以后臺定位、無藍(lán)條 |
| requestWhenInUseAuthorization | 可以前臺定位、不可以后臺定位、無藍(lán)條 | 可以前臺定位、可以后臺定位、有藍(lán)條 |
| 無/用戶拒絕 | 無任何定位 | 無任何定位 |
****iOS9.0 - iOS12.0****
| | Capabilities 關(guān) | | Capabilities 開 | |
| --- | :---: | :---: | --- | --- |
| | allowsBackgroundLocationUpdates關(guān) | allowsBackgroundLocationUpdates開 | allowsBackgroundLocationUpdates關(guān) | allowsBackgroundLocationUpdates開 |
| requestAlwaysAuthorization | 可以前臺定位、不可以后臺定位、無藍(lán)條 | iOS拋出Crash | 可以前臺定位、不可以后臺定位、無藍(lán)條 | 可以前臺定位、可以后臺定位、無藍(lán)條 |
| requestWhenInUseAuthorization | 可以前臺定位、不可以后臺定位、無藍(lán)條 | iOS拋出Crash | 可以前臺定位、不可以后臺定位、無藍(lán)條 | 可以前臺定位、可以后臺定位、有藍(lán)條 |
| 無/用戶拒絕 | 無任何定位 | iOS拋出Crash | 無任何定位 | 無任何定位 |
三、獲取定位數(shù)據(jù)
1、單次定位
-iOS8.0版本不支持單次定位,需要調(diào)用連續(xù)定位startUpdatingLocation接口,自行實(shí)現(xiàn)單次定位功能;
-iOS9.0及以后版本,可以調(diào)用單次定位API:
-(void)requestLocation API_AVAILABLE(ios(9.0));
2、連續(xù)定位
-開始連續(xù)定位:- (void)startUpdatingHeading;
-停止連續(xù)定位:- (void)stopUpdatingHeading;
3、定位CLLocationManager相關(guān)屬性
-定位活動類型@property(assign, nonatomic) CLActivityType activityType;
-typedef NS_ENUM(NSInteger, CLActivityType) {
CLActivityTypeOther = 1, //未知類型,默認(rèn)值
CLActivityTypeAutomotiveNavigation, //駕車導(dǎo)航定位
CLActivityTypeFitness, //健身活動,如步行、跑步、騎車等;
CLActivityTypeOtherNavigation, //其他交通工具導(dǎo)航,如火車、輪船等
CLActivityTypeAirborne //空中飛行定位(iOS12及以上版本)
};
-設(shè)置期望的定位精度@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;
-當(dāng)精度設(shè)置較高時(shí),定位服務(wù)會盡可能去獲取滿足desiredAccuracy的定位結(jié)果,但不一定會得到滿足期望的結(jié)果;
-kCLLocationAccuracyReduced為iOS14新特性,模糊定位,即使當(dāng)前精確定位開啟,如果設(shè)置該值,則會收到模糊定位結(jié)果;
-取值范圍:
kCLLocationAccuracyBestForNavigation; //導(dǎo)航高精度
kCLLocationAccuracyBest; //高精度
kCLLocationAccuracyNearestTenMeters; //10米
kCLLocationAccuracyHundredMeters; //100米
kCLLocationAccuracyKilometer; //1000米
kCLLocationAccuracyThreeKilometers; //3000米
kCLLocationAccuracyReduced; //模糊定位,誤差5000米(iOS14及以上版本)
-設(shè)置定位的最小更新距離@property(assign, nonatomic) CLLocationDistance distanceFilter;
-單位米,默認(rèn)為 kCLDistanceFilterNone,表示只要檢測到設(shè)備位置發(fā)生變化就會更新位置信息;
-@property(nonatomic, assign) BOOL pausesLocationUpdatesAutomatically;
-是否允許系統(tǒng)自動暫停定位功能,設(shè)置為YES進(jìn)行后臺定位時(shí),系統(tǒng)檢測到長時(shí)間沒有位置更新的時(shí)候,將會暫停定位功能,當(dāng)app進(jìn)入前臺時(shí)會恢復(fù)定位功能;
3、定位數(shù)據(jù)更新回調(diào)
--(void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations;
-locations是按時(shí)間排序的CLLocation對象數(shù)組,一般使用lastObject即為當(dāng)前最新定位信息;
四、定位權(quán)限API調(diào)用實(shí)踐
1、配置info.plist
-如果不需要使用后臺定位,則無需配置NSLocationAlwaysAndWhenInUseUsageDescription、NSLocationAlwaysUsageDescription字段,并且代碼(包括使用的靜態(tài)庫)中不能出現(xiàn)requestAlwaysAuthorization符號;
[圖片上傳失敗...(image-3fd3da-1637218463749)]
2、開始定位
-此處直接在主線程開始定位,如果需要在子線程開始定位,則需要開啟子線程的runloop,此處不再累述。自蘋果X后,如果在子線程開始定位,會有UI不在主線程調(diào)用的警告,直接屏蔽或者忽略即可,不影響正常使用;
//前置步驟:創(chuàng)建定位管理類CLLocationManager,配置定位參數(shù)
//開始定位
- (void)startLocation{
//self.locationManager = [[CLLocationManager alloc]init];
//self.locationManager.allowsBackgroundLocationUpdates = YES;
//self.locationManager.delegate = self;
if([self locationServiceIsValid] == NO){
NSLog(@"用戶拒絕該app使用定位服務(wù)");
return;
}
//該場景下是否需要精確定位
BOOL isNeedFullAccuracy = YES;
//該場景下如果需要精確定位,則對應(yīng)的plist中配置的key
NSString *purposeKey = @"ExampleUsageDescription";
//判斷當(dāng)前定位權(quán)限是否ok
[self checkLocationAuthorizationStatus:self.locationManager
needFullAccuracy:isNeedFullAccuracy
purposeKey:purposeKey];
//開始連續(xù)定位
[self.locationManager startUpdatingLocation];
}
3、獲取當(dāng)前定位權(quán)限
//獲取當(dāng)前定位權(quán)限
- (CLAuthorizationStatus)authorizationStatus
{
if (@available(iOS 14.0, *)) {
return self.locationManager.authorizationStatus;
} else {
return [CLLocationManager authorizationStatus];
}
}
//當(dāng)前應(yīng)用是否可以使用/申請定位服務(wù)
- (BOOL)locationServiceIsValid{
if ([self authorizationStatus] == kCLAuthorizationStatusDenied ||
[self authorizationStatus] == kCLAuthorizationStatusRestricted) {
return NO;
}
return YES;
}
//當(dāng)前定位狀態(tài)是否可用
- (BOOL)locationAuthStatusIsValid{
if ([self locationServiceIsValid] == NO) {
return NO;
}
if ([self authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
return NO;
}
return YES;
}
4、核實(shí)當(dāng)前權(quán)限狀態(tài),判斷是否需要申請權(quán)限或者權(quán)限升級
-****如果app需要使用后臺定位****
//核實(shí)當(dāng)前權(quán)限狀態(tài),判斷是否需要申請權(quán)限或者權(quán)限升級
- (void)checkLocationAuthorizationStatus:(CLLocationManager *)manager
needFullAccuracy:(BOOL)isNeedFullAccuracy
purposeKey:(NSString *)purposeKey{
if([self authorizationStatus] == kCLAuthorizationStatusNotDetermined){
//如果沒有定位權(quán)限,則需要先申請定位權(quán)限
//如果是iOS14申請權(quán)限彈窗時(shí)可以選擇精度開關(guān),所以不用在單獨(dú)處理精度權(quán)限
[self requestLocationAuthorizationIfNeed:manager];
}else if(isNeedFullAccuracy){
//如果已經(jīng)有定位權(quán)限且需要精確定位
[self requestTemporaryFullAccuracyAuthorizationIfNeed:manager purposeKey:purposeKey];
}
}
//如果當(dāng)前場景需要精確定位,則可以申請一次臨時(shí)精確定位
- (void)requestTemporaryFullAccuracyAuthorizationIfNeed:(CLLocationManager *)manager
purposeKey:(NSString *)purposeKey
{
//如果是非iOS14系統(tǒng),則默認(rèn)為精確定位
if (@available(iOS 14.0, *)) {
//如果已經(jīng)獲得定位權(quán)限,但精度權(quán)限只是模糊定位
if (manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy) {
NSDictionary *locationTemporaryDictionary = [[NSBundle mainBundle]
objectForInfoDictionaryKey:@"NSLocationTemporaryUsageDescriptionDictionary"];
BOOL hasLocationTemporaryKey = locationTemporaryDictionary != nil && locationTemporaryDictionary.count != 0;
if (hasLocationTemporaryKey) {
//此API不能用于申請定位權(quán)限,只能用于從模糊定位升級為精確定位;申請定位權(quán)限只能調(diào)用
//requestWhen或requestAlways,如果沒有獲得定位權(quán)限,直接調(diào)用此API無效。
[manager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:purposeKey completion:nil];
}else{
NSLog(@"如果需要使用臨時(shí)精確定位,需要在Info.plist中添加 \
NSLocationTemporaryUsageDescriptionDictionary字段。");
}
}
}
}
//請求定位權(quán)限,
- (void)requestLocationAuthorizationIfNeed:(CLLocationManager *)manager
{
//系統(tǒng)版本號
CGFloat systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
//系統(tǒng)版本8+ && 沒有選擇過定位權(quán)限
if (systemVersion > 7.99 && [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined)
{
//獲取info.plist中配置字段信息
BOOL hasAlwaysKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] != nil;
BOOL hasWhenInUseKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] != nil;
BOOL hasAlwaysAndWhenInUseKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"] != nil;
//如果是iOS11及以后版本。(當(dāng)前iOS11到13居多)
if (@available(iOS 11.0, *)){
if (hasAlwaysAndWhenInUseKey && hasWhenInUseKey)
{
//如果plist同時(shí)配置兩個(gè)字段,則兩個(gè)權(quán)限申請API都可以調(diào)用;
//建議直接調(diào)用requestAlwaysAuthorization即可
[manager requestAlwaysAuthorization];
}
else if (hasWhenInUseKey)
{
//如果plist只配置InUseKey,則只能調(diào)用使用時(shí)API
[manager requestWhenInUseAuthorization];
}
else{
NSLog(@"要在iOS11及以上版本使用定位服務(wù), 需要在Info.plist中添加 \
NSLocationAlwaysAndWhenInUseUsageDescription和NSLocationWhenInUseUsageDescription字段。");
}
}
else
{
if (hasAlwaysKey)
{
//如果plist配置hasAlwaysKey,則可以調(diào)用始終允許API
[manager requestAlwaysAuthorization];
}
else if (hasWhenInUseKey)
{
//如果plist配置hasAlwaysKey,則可以調(diào)用始終允許API
[manager requestWhenInUseAuthorization];
}
else
{
NSLog(@"要在iOS8到iOS10版本使用定位服務(wù), 需要在Info.plist中添加 \
NSLocationAlwaysUsageDescription或者NSLocationWhenInUseUsageDescription字段。");
}
}
}
}
-****如果app不需要使用后臺定位****
//核實(shí)當(dāng)前權(quán)限狀態(tài),判斷是否需要申請權(quán)限或者權(quán)限升級
- (void)checkLocationAuthorizationStatus:(CLLocationManager *)manager
needFullAccuracy:(BOOL)isNeedFullAccuracy
purposeKey:(NSString *)purposeKey{
if([self authorizationStatus] == kCLAuthorizationStatusNotDetermined){
//如果沒有定位權(quán)限,則需要先申請定位權(quán)限
//如果是iOS14申請權(quán)限彈窗時(shí)可以選擇精度開關(guān),所以不用在單獨(dú)處理精度權(quán)限
[self requestLocationAuthorizationIfNeed:manager];
}else if(isNeedFullAccuracy){
//如果已經(jīng)有定位權(quán)限且需要精確定位
[self requestTemporaryFullAccuracyAuthorizationIfNeed:manager purposeKey:purposeKey];
}
}
//如果當(dāng)前場景需要精確定位,則可以申請一次臨時(shí)精確定位
- (void)requestTemporaryFullAccuracyAuthorizationIfNeed:(CLLocationManager *)manager
purposeKey:(NSString *)purposeKey
{
//如果是非iOS14系統(tǒng),則默認(rèn)為精確定位
if (@available(iOS 14.0, *)) {
//如果已經(jīng)獲得定位權(quán)限,但精度權(quán)限只是模糊定位
if (manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy) {
NSDictionary *locationTemporaryDictionary = [[NSBundle mainBundle]
objectForInfoDictionaryKey:@"NSLocationTemporaryUsageDescriptionDictionary"];
BOOL hasLocationTemporaryKey = locationTemporaryDictionary != nil && locationTemporaryDictionary.count != 0;
if (hasLocationTemporaryKey) {
//此API不能用于申請定位權(quán)限,只能用于從模糊定位升級為精確定位;申請定位權(quán)限只能調(diào)用
//requestWhen或requestAlways,如果沒有獲得定位權(quán)限,直接調(diào)用此API無效。
[manager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:purposeKey completion:nil];
}else{
NSLog(@"如果需要使用臨時(shí)精確定位,需要在Info.plist中添加 \
NSLocationTemporaryUsageDescriptionDictionary字段。");
}
}
}
}
//請求定位權(quán)限,
- (void)requestLocationAuthorizationIfNeed:(CLLocationManager *)manager
{
//系統(tǒng)版本號
CGFloat systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
//系統(tǒng)版本8+ && 沒有選擇過定位權(quán)限
if (systemVersion > 7.99 && [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined)
{
//獲取info.plist中配置字段信息
BOOL hasWhenInUseKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] != nil;
if (hasWhenInUseKey)
{
//如果plist配置InUseKey,則只能調(diào)用使用時(shí)API
[manager requestWhenInUseAuthorization];
}
else{
NSLog(@"要在iOS8及以上版本使用定位服務(wù), 需要在Info.plist中添加 \
NSLocationWhenInUseUsageDescription字段。");
}
}
}
5、定位權(quán)限狀態(tài)變更
//iOS13及以前版本回調(diào)
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
[self locationStatusDidChanged:[CLLocationManager authorizationStatus]];
}
//iOS14及以后版本回調(diào)
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager
{
if (@available(iOS 14.0, *)) {
[self locationStatusDidChanged:manager.authorizationStatus];
if([self locationAuthStatusIsValid]){
CLAccuracyAuthorization accuracyAuth = manager.accuracyAuthorization;
if (accuracyAuth == CLAccuracyAuthorizationReducedAccuracy){
NSLog(@"TODO: 可以模糊定位");
}else{
NSLog(@"TODO: 可以精確定位");
}
//該場景下是否需要精確定位
BOOL isNeedFullAccuracy = YES;
if (isNeedFullAccuracy == YES && accuracyAuth == CLAccuracyAuthorizationReducedAccuracy) {
NSLog(@"TODO: 該場景需要精確定位才可以使用,請去設(shè)置中打開精確定位開關(guān)");
}
}
} else {
}
}
- (void)locationStatusDidChanged:(CLAuthorizationStatus)authStatus
{
switch (authStatus) {
case kCLAuthorizationStatusNotDetermined:
NSLog(@"可以申請定位權(quán)限");
break;
case kCLAuthorizationStatusRestricted:
case kCLAuthorizationStatusDenied:
NSLog(@"TODO: 沒有定位權(quán)限");
break;
case kCLAuthorizationStatusAuthorizedAlways:
case kCLAuthorizationStatusAuthorizedWhenInUse:
NSLog(@"TODO: 擁有定位權(quán)限");
default:
break;
}
}
6、定位回調(diào)
//定位回調(diào)
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
CLLocation *locationg = locations.lastObject;
NSLog(@"TODO: 收到定位數(shù)據(jù):%@",locationg);
}
五、小結(jié)
定位信息作為用戶非常在意的隱私數(shù)據(jù),iOS開發(fā)者應(yīng)盡量遵循適用原則(即能滿足需求的最小權(quán)限)去獲取用戶定位信息。本文對iOS系統(tǒng)定位權(quán)限說明從8.0到14.0,其中關(guān)于API調(diào)用實(shí)踐是對應(yīng)的最大定位權(quán)限,開發(fā)者可以根據(jù)需求參考相對應(yīng)的部分。