使用定位服务,需要引入头文件:
#import <CoreLocation/CoreLocation.h>
定位服务的管理类,相关服务的开始/结束都是以此类发起。
CLLocationManager 可以实现以下功能:
- 一般定位
- 后台定位
- 监听设备朝向
- 监听进入/离开某个区域
- 检测周边 iBeacon
1. 申请权限
使用定位服务,需要向用户申请权限,即在Info.plist中添加以下key,并详细描述使用该权限的原因:
- NSLocationAlwaysUsageDescription
申请一直使用定位权限,前台/后台- NSLocationWhenInUseUsageDescription
申请使用应用期间的定位权限- 需要注意的是,添加以上两个key的同时,还需要添加下面这个key
NSLocationAlwaysAndWhenInUseUsageDescription
所有key,都要详细说明申请该权限的原因,否则会因为权限问题审核悲剧
权限的申请,主要用到以下API:
// 定位服务是否可用,用于判断用户是否关闭了定位服务,使用定位服务,应该确保用户开启了相关服务。
+ (BOOL)locationServicesEnabled
// 获取当前授权状态
+ (CLAuthorizationStatus)authorizationStatus
// 请求在使用应用期间的位置权限,需要在Info.plist文件配置 NSLocationWhenInUseUsageDescription,其值为使用该权限的详细描述
- (void)requestWhenInUseAuthorization
// 请求一直使用位置服务的权限,需要在Info.plist文件配置 NSLocationAlwaysAndWhenInUseUsageDescription 和 NSLocationWhenInUseUsageDescription ,其值为使用该权限的详细描述。
- (void)requestAlwaysAuthorization
发起权限申请后,当用户做出选择时,或者当前权限状态改变时,会调用下面的代理方法:
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
需要使用代理的地方,肯定得设置代理,下同:
// 代理,相关变化/数据,在代理方法里获取
@property(assign, nonatomic, nullable) id<CLLocationManagerDelegate> delegate;
一般我们会这样使用相关的API:
// 先判断是否可使用定位服务
if ([CLLocationManager locationServicesEnabled] == NO) {
return ;
}
// 获取当前授权状态
CLAuthorizationStatus status = [CLLocationManager authorizationStatus] ;
switch (status) {
case kCLAuthorizationStatusAuthorizedWhenInUse:
case kCLAuthorizationStatusAuthorizedAlways:
[[LQLocationManager defaultManager].locationManager startUpdatingLocation];
break;
case kCLAuthorizationStatusDenied:
// 用户拒绝使用定位,可在此引导用户开启
NSLog(@"用户拒绝使用定位");
break;
case kCLAuthorizationStatusRestricted:
// 权限受限,可引导用户开启
break;
case kCLAuthorizationStatusNotDetermined:
// 未选择,一般是首次启动,根据需要发起申请
[[LQLocationManager defaultManager].locationManager requestAlwaysAuthorization];
[[LQLocationManager defaultManager].locationManager requestWhenInUseAuthorization];
break;
default:
break;
}
在代理方法中,如果检测到权限变更,需要根据变更后的权限进行相应的操作:
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
switch (status) {
case kCLAuthorizationStatusAuthorizedWhenInUse:
case kCLAuthorizationStatusAuthorizedAlways:
[manager startUpdatingLocation];
break;
case kCLAuthorizationStatusDenied:
// 用户拒绝使用定位,可在此引导用户开启
break;
case kCLAuthorizationStatusRestricted:
// 权限受限,可引导用户开启
break;
case kCLAuthorizationStatusNotDetermined:
// 未选择,在代理方法里,一般不会有这个状态,如果有m,再次发起申请
break;
default:
break;
}
}
如果是需要后台定位服务的权限,还需在项目中进行配置:在Capabilities 打开 Background Modes,并勾选 Location updates
PS: 以下使用到代理的地方,都需要给locationManager 设置代理
2. 标准定位
发起定位有两种方式,一种是一般的定位请求,一种是显著的位置变化才会触发的定位请求
// 指定活动类型,详见下方 CLActivityType
@property(assign, nonatomic) CLActivityType activityType
// 定位距离过滤器,如果使用kCLDistanceFilterNone,则任何移动都会调用代理
@property(assign, nonatomic) CLLocationDistance distanceFilter;
// 定位精准度,一般使用kCLLocationAccuracyBest,相见下方CLLocationAccuracy
@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;
// 在某些情况下,是否自动暂停定位,例如被系统某些应用打断
@property(assign, nonatomic) BOOL pausesLocationUpdatesAutomatically
// 是否允许后台定位,需要项目配置相应权限
@property(assign, nonatomic) BOOL allowsBackgroundLocationUpdates
// 后台定位时,在状态栏显示提示
@property(assign, nonatomic) BOOL showsBackgroundLocationIndicator
// 请求在使用期间的定位权限
- (void)requestWhenInUseAuthorization
// 请求一直使用定位权限
- (void)requestAlwaysAuthorization
// 一般情况下开始/结束定位,有位置变化都会调用代理方法
// locationManager:didUpdateLocations:
- (void)startUpdatingLocation
- (void)stopUpdatingLocation
// 只会获取一次位置
- (void)requestLocation
// 显著的位置变化才会调用代理方法
// locationManager:didUpdateLocations:
// 另外,在app被杀死的情况下也会调用,需要有后台权限,在后台定位时使用
- (void)startMonitoringSignificantLocationChanges
- (void)stopMonitoringSignificantLocationChanges
// 设备是否支持显著位置变化监视
+ (BOOL)significantLocationChangeMonitoringAvailable
另外,还有延迟一定距离,或者一定时间再去触发位置更新的API,只不过其使用条件比较苛刻,顾不常使用,详见文章最后部分的解释,其API为:
// 设备是否支持延迟触发
+ (BOOL)deferredLocationUpdatesAvailable
// 设置延迟的参数,一定距离/一定时间
- (void)allowDeferredLocationUpdatesUntilTraveled:(CLLocationDistance)distance
timeout:(NSTimeInterval)timeout
// 取消延迟
- (void)disallowDeferredLocationUpdates
摘录部分使用条件:
- 硬件设备必须是iPhone 5 +才支持
- distanceFilter 属性必须为 kCLDistanceFilterNone
- desiredAccuracy 属性必须为kCLLocationAccuracyBest或者kCLLocationAccuracyBestForNavigation
- 系统必须在低功耗状态才有效,这点比较苛刻
获取到新的位置信息后,会调用相关的代理方法:
// 位置有更新的代理
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray<CLLocation *> *)locations
// 失败时调用
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
// 暂停/继续时调用
- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
- (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager
CLLocation
该类包含了所有的和位置相关的信息,主要是通过其属性 coordinate 来获取当前的经纬度,其他的属性都是一些定位相关的配置信息。可以直接使用CLLocation实例通过CLGeocoder进行反编译出具体的地理位置
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler
CLPlacemark
通过CLGeocoder反编译后获取的包含实际地理位置信息的实例对象,主要是下面这些信息:
// 地标名称,一般是某个地区地标式建筑/地名等,可能为空;例如:中关村
@property (nonatomic, readonly, copy, nullable) NSString *name;
// 街道名称,例如:中关村大街甲1号
@property (nonatomic, readonly, copy, nullable) NSString *thoroughfare;
// 街道附加信息,例如门牌号等
@property (nonatomic, readonly, copy, nullable) NSString *subThoroughfare;
// 地级市/直辖市,例如:北京市
@property (nonatomic, readonly, copy, nullable) NSString *locality;
// 县/区,例如:海淀区
@property (nonatomic, readonly, copy, nullable) NSString *subLocality;
// 一个行政区域,一般是省
@property (nonatomic, readonly, copy, nullable) NSString *administrativeArea;
// 镇,在直辖市中可能为空
@property (nonatomic, readonly, copy, nullable) NSString *subAdministrativeArea;
// 邮编
@property (nonatomic, readonly, copy, nullable) NSString *postalCode;
// 国家编码,例如:CN 中国
@property (nonatomic, readonly, copy, nullable) NSString *ISOcountryCode;
// 国家名称
@property (nonatomic, readonly, copy, nullable) NSString *country;
// 湖泊/水源名称
@property (nonatomic, readonly, copy, nullable) NSString *inlandWater;
// 大洋名称
@property (nonatomic, readonly, copy, nullable) NSString *ocean;
// 周边景点集合
@property (nonatomic, readonly, copy, nullable) NSArray<NSString *> *areasOfInterest;
一般使用:
在获取权限后,调用startUpdatingLocation
[manager startUpdatingLocation];
在代理方法获取位置信息
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
if (locations && locations.count > 0) {
// 根据实际需求,是只取一个,还是都要
// CLLocation *location = [locations firstObject];
// [self reverseGeocodeWithLocation:location];
for (CLLocation *location in locations) {
// 根据获取到的location实例,反编译地理位置信息
[self reverseGeocodeWithLocation:location];
}
// 根据需要,获取位置成功后是否要停止定位
[manager stopUpdatingLocation];
}
}
// 反编译地理信息
- (void) reverseGeocodeWithLocation:(CLLocation *) location {
if (!location) {
return ;
}
CLGeocoder *coder = [[CLGeocoder alloc]init];
[coder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (placemarks && placemarks.count > 0) {
CLPlacemark *mark = [placemarks firstObject];
// 地标名称
NSLog(@"name %@", mark.name);
// 街道名称
NSLog(@"thoroughfare %@", mark.thoroughfare);
// 街道附加信息,例如门牌号
NSLog(@"subThoroughfare %@", mark.subThoroughfare);
// 地级市/直辖市
NSLog(@"locality %@", mark.locality);
// 区/县
NSLog(@"subLocality %@", mark.subLocality);
// 省
NSLog(@"administrativeArea %@", mark.administrativeArea);
// 行政区附加信息
NSLog(@"subAdministrativeArea %@", mark.subAdministrativeArea);
// 邮编
NSLog(@"postalCode %@", mark.postalCode);
// 国家编码,中国CN
NSLog(@"ISOcountryCode %@", mark.ISOcountryCode);
// 国家名称
NSLog(@"country %@", mark.country);
// 水源/湖泊
NSLog(@"inlandWater %@", mark.inlandWater);
// 海洋
NSLog(@"ocean %@", mark.ocean);
// 景点
NSLog(@"areasOfInterest %@", mark.areasOfInterest);
}
}];
}
在使用 startMonitoringSignificantLocationChanges 时,需要判断设备是否支持,可以这样使用:
if ([CLLocationManager significantLocationChangeMonitoringAvailable] == NO) {
return ;
}
// 显著位置变化时调用locationManager:didUpdateLocations
// 在app被杀死的状态下也能调用,需要开启后台定位权限
[[LQLocationManager defaultManager].locationManager startMonitoringSignificantLocationChanges];
3. 后台定位
后台定位的很多设置和代理方法都和标准定位一样,区别是在app被杀死状态下也能实现定位,需要一些额外的配置;
- 必须配置:在Capabilities 打开 Background Modes,并勾选 Location updates
- 必须使用 requestAlwaysAuthorization 申请一直使用定位权限
- 需要设置 allowsBackgroundLocationUpdates 为YES
根据自己需要调用startUpdatingLocation,或者startMonitoringSignificantLocationChanges发起定位,其区别可以参考这篇文章
关于后台定位,可以参考这篇文章的应用,其实现流程和精准定位差别不大,只是需要申请特别的权限。
4.设备朝向
获取设备的朝向比较简单,其功能类似指南针/陀螺仪,能够获取设备朝向相对于地磁北极/实际北极的角度,x/y/z方向的值等:
// 设备朝向是否可用
+ (BOOL)headingAvailable
// 开始监控设备朝向
- (void)startUpdatingHeading
// 停止监控
- (void)stopUpdatingHeading
// 取消校准
- (void)dismissHeadingCalibrationDisplay
// 角度变化的灵敏度,默认是1
@property(assign, nonatomic) CLLocationDegrees headingFilter
// 设备的参考方向,默认是CLDeviceOrientationPortrait
@property(assign, nonatomic) CLDeviceOrientation headingOrientation
// 最后一次更新的方向
@property(readonly, nonatomic, copy, nullable) CLHeading *heading
其值的变化,会在调用下面的代理方法:
// 朝向变化时调用
- (void)locationManager:(CLLocationManager *)manager
didUpdateHeading:(CLHeading *)newHeading
// 是否返回新的方向的校准信息
- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager
CLHeading
设备朝向的相关信息封装在CLHeading类中:
//相对于地磁北极的角度
@property(readonly, nonatomic) CLLocationDirection magneticHeading;
// 相对于地理北极的角度
@property(readonly, nonatomic) CLLocationDirection trueHeading;
// 最大偏差
@property(readonly, nonatomic) CLLocationDirection headingAccuracy;
// x
@property(readonly, nonatomic) CLHeadingComponentValue x;
// y
@property(readonly, nonatomic) CLHeadingComponentValue y;
// z
@property(readonly, nonatomic) CLHeadingComponentValue z;
// 时间戳
@property(readonly, nonatomic, copy) NSDate *timestamp;
使用
开始朝向监控
if ([CLLocationManager headingAvailable] == NO) {
return ;
}
[[LQLocationManager defaultManager].locationManager startUpdatingHeading];
检测到新的朝向会调用代理方法
- (void)locationManager:(CLLocationManager *)manager
didUpdateHeading:(CLHeading *)newHeading {
NSLog(@"%f", newHeading.magneticHeading);
NSLog(@"%f", newHeading.trueHeading);
NSLog(@"%f", newHeading.headingAccuracy);
NSLog(@"%f", newHeading.x);
NSLog(@"%f", newHeading.y);
NSLog(@"%f", newHeading.z);
}
- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager {
return YES;
}
5. 区域监测
CLLocationManager 可以监控是否进入/离开指定的区域,在进入/离开某些区域时进行相关的消息派发;例如:当进入某商业区范围,可以推送周边的优惠消息等。
// 是否支持对指定区域进行监视
+ (BOOL)isMonitoringAvailableForClass:(Class)regionClass
// 开始监控某个区域
// 参数accuracy为设置当超过区域边界该距离时才会触发
// 如果传入的监控实例region与以监控的实例有相同的identifier,该区域监控将被移除,所以需要注意不要重复添加
- (void)startMonitoringForRegion:(CLRegion *)region
desiredAccuracy:(CLLocationAccuracy)accuracy
- (void)startMonitoringForRegion:(CLRegion *)region
// 停止监控某个区域
- (void)stopMonitoringForRegion:(CLRegion *)region
API_AVAILABLE(ios(5.0), macos(10.8)) API_UNAVAILABLE(watchos, tvos);
//获取当前位置在某个区域的状态:在区域内还是区域外
// 会调用 locationManager:didDetermineState:forRegion:
- (void)requestStateForRegion:(CLRegion *)region
相关代理方法:
// 开始监控某个区域时调用
- (void)locationManager:(CLLocationManager *)manager
didStartMonitoringForRegion:(CLRegion *)region
// 进入监控区域时调用
- (void)locationManager:(CLLocationManager *)manager
didEnterRegion:(CLRegion *)region
// 离开监控区域时调用
- (void)locationManager:(CLLocationManager *)manager
didExitRegion:(CLRegion *)region
// 出错时调用
- (void)locationManager:(CLLocationManager *)manager
monitoringDidFailForRegion:(nullable CLRegion *)region
withError:(NSError *)error
// 使用方法requestStateForRegion获取某个区域状态时调用
- (void)locationManager:(CLLocationManager *)manager
didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
这里用到的 CLRegion 参数,现在一般使用其子类 CLCircularRegion,主要是一个初始化方法
// center:经纬度二维中心点
// radius:圆形区域半径
// identifier:标识符
- (instancetype)initWithCenter:(CLLocationCoordinate2D)center
radius:(CLLocationDistance)radius
identifier:(NSString *)identifier;
一般我们可以这样使用相关的功能,开始监测
if ([CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
// 请求一次当前位置
[[LQLocationManager defaultManager].locationManager requestLocation];
// 39.987041,116.327371
// 39.990068, 116.321172
CLLocationCoordinate2D coor = CLLocationCoordinate2DMake(39.987041,116.327371);
CLCircularRegion *regin = [[CLCircularRegion alloc]initWithCenter:coor radius:10 identifier:@"reginid"];
[[LQLocationManager defaultManager].locationManager startMonitoringForRegion:regin];
} else {
NSLog(@"不支持监控区域");
}
需要注意,使用这个功能,我们需要调用startUpdatingLocation来定位当前所在位置,所以需要结合第2条的内容实现。所以,我们可以使用requestLocation 请求一次当前位置,也可以不用,视情况而定。
代理回调:
- (void)locationManager:(CLLocationManager *)manager
didEnterRegion:(CLRegion *)region {
// 进入区域
}
- (void)locationManager:(CLLocationManager *)manager
didExitRegion:(CLRegion *)region {
// 离开区域
}
- (void)locationManager:(CLLocationManager *)manager
monitoringDidFailForRegion:(nullable CLRegion *)region
withError:(NSError *)error {
// 出错
}
6. 检测周边 iBeacon
关于这个,没有具体使用,只是顺便看了相关的API,其使用和其他区别不大:
+ (BOOL)isRangingAvailable
- (void)startRangingBeaconsInRegion:(CLBeaconRegion *)region
- (void)stopRangingBeaconsInRegion:(CLBeaconRegion *)region
// 代理方法
- (void)locationManager:(CLLocationManager *)manager
didRangeBeacons:(NSArray<CLBeacon *> *)beacons inRegion:(CLBeaconRegion *)region
- (void)locationManager:(CLLocationManager *)manager
rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region
withError:(NSError *)error
相关介绍可以参考这篇文章
附加
CLActivityType
typedef NS_ENUM(NSInteger, CLActivityType) {
CLActivityTypeOther = 1,
// 汽车导航
CLActivityTypeAutomotiveNavigation,
// 行人活动
CLActivityTypeFitness,
// 其他导航,船/火车/飞机
CLActivityTypeOtherNavigation,
// 机载
CLActivityTypeAirborne
};
定位精度 desiredAccuracy
// 导航用精度
kCLLocationAccuracyBestForNavigation
// 一般精确定位
kCLLocationAccuracyBest
// 10米
kCLLocationAccuracyNearestTenMeters
// 100米
kCLLocationAccuracyHundredMeters
// 1000米
kCLLocationAccuracyKilometer
// 3000米
kCLLocationAccuracyThreeKilometers
CLAuthorizationStatus
typedef NS_ENUM(int, CLAuthorizationStatus) {
// 用户还未选择,一般初次使用时为该状态
kCLAuthorizationStatusNotDetermined = 0,
// 位置服务受限
kCLAuthorizationStatusRestricted,
// 用户拒绝使用位置服务
kCLAuthorizationStatusDenied,
// 用户同意一直使用位置服务,前台/后台
kCLAuthorizationStatusAuthorizedAlways API_AVAILABLE(macos(10.12), ios(8.0)),
// 用户仅同意在应用使用期间使用位置服务
kCLAuthorizationStatusAuthorizedWhenInUse API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(macos),
};
网友评论