美文网首页
定位 CLLocationManager 相关API

定位 CLLocationManager 相关API

作者: 流火绯瞳 | 来源:发表于2018-12-07 10:26 被阅读30次

    使用定位服务,需要引入头文件:

    #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),
    };
    
    

    相关文章

      网友评论

          本文标题:定位 CLLocationManager 相关API

          本文链接:https://www.haomeiwen.com/subject/ppdfcqtx.html