CoreLocation

一、简介

在移动互联网时代,移动app能解决用户的很多生活琐事,比如
周边:找餐馆、找KTV、找电影院等等
导航:根据用户设定的起点和终点,进行路线规划,并指引用户如何到达

在上述应用中,都用到了定位和地图功能,在iOS开发中,要想加入这2大功能,必须基于2个框架进行开发
CoreLocation :用于地理定位,地理编码,区域监听等(着重功能实现)
MapKit :用于地图展示,例如大头针,路线、覆盖层展示等(着重界面展示)

2个热门专业术语
LBS :Location Based Service
SoLoMo :Social Local Mobile(索罗门)

二、CoreLocation框架的使用

导入主头文件

import

CoreLocation框架使用须知
CoreLocation框架中所有数据类型的前缀都是CL
CoreLocation中使用CLLocationManager对象来做用户定位

三、CLLocationManager

CLLocationManager的常用操作

开始更新用户位置
- (void)startUpdatingLocation;

停止更新用户位置
- (void) stopUpdatingLocation;

当调用了startUpdatingLocation方法后,就开始不断地请求、刷新用户的位置,一旦请求到用户位置就会调用代理的下面方法
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
locations参数里面装着CLLocation对象
为了严谨起见,最好在使用定位功能之前判断当前应用的定位功能是否可用
CLLocationManager有个类方法可以判断当前应用的定位功能是否可用
  + (BOOL)locationServicesEnabled;

@property(assign, nonatomic) CLLocationDistance distanceFilter;
每隔多少米定位一次

@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;
定位精确度(越精确就越耗电)

四、CLLocation

CLLocation用来表示某个位置的地理信息,比如经纬度、海拔等等
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate;
经纬度

@property(readonly, nonatomic) CLLocationDistance altitude;
海拔

@property(readonly, nonatomic) CLLocationDirection course;
路线,航向(取值范围是0.0° ~ 359.9°,0.0°代表真北方向)

@property(readonly, nonatomic) CLLocationSpeed speed;
移动速度(单位是m/s)

用- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location方法可以计算2个位置之间的距离

五、CLLocationCoordinate2D

CLLocationCoordinate2D是一个用来表示经纬度的结构体,定义如下
typedef struct {
        CLLocationDegrees latitude; // 纬度
        CLLocationDegrees longitude; // 经度
} CLLocationCoordinate2D;

一般用CLLocationCoordinate2DMake函数来创建CLLocationCoordinate2D
CoreLocation_第1张图片
屏幕快照 2017-06-25 下午6.34.44.png

天朝的经纬度范围
纬度范围:N 3°51′ ~ N 53°33′
经度范围:E 73°33′ ~ E 135°05′
部分城市的经纬度

CoreLocation_第2张图片
屏幕快照 2017-06-25 下午6.37.14.png

如果是模拟器,需要设置模拟位置(经纬度)
天朝帝都的经纬度是:北纬40°,东经116°

CoreLocation_第3张图片
图片 1.png

六、CLGeocoder

使用CLGeocoder可以完成“地理编码”和“反地理编码”
地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
反地理编码:根据给定的经纬度,获得具体的位置信息

地理编码方法

- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;

反地理编码方法

- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;

当地理\反地理编码完成时,就会调用CLGeocodeCompletionHandler
typedef void (^CLGeocodeCompletionHandler)(NSArray *placemarks, NSError *error);
这个block传递2个参数
error :当编码出错时(比如编码不出具体的信息)有值
placemarks :里面装着CLPlacemark对象
CLPlacemark的字面意思是地标,封装详细的地址位置信息

@property (nonatomic, readonly) CLLocation *location;
地理位置

@property (nonatomic, readonly) CLRegion *region;
区域

@property (nonatomic, readonly) NSDictionary *addressDictionary;
详细的地址信息

@property (nonatomic, readonly) NSString *name;
地址名称

@property (nonatomic, readonly) NSString *locality;
城市
CoreLocation_第4张图片
屏幕快照 2017-06-25 下午6.45.41.png

七、用户隐私的保护

从iOS 6开始,苹果在保护用户隐私方面做了很大的加强,以下操作都必须经过用户批准授权
要想获得用户的位置
想访问用户的通讯录、日历、相机、相册等等

当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权
开发者可以在Info.plist中设置NSLocationUsageDescription说明定位的目的(Privacy - Location Usage Description)

图片 1.png
CoreLocation_第5张图片
图片 1.png

一旦用户选择了“Don’t Allow”,意味着你的应用以后就无法使用定位功能
为了提高用户的授权概率,段子一定要写好!

八、iOS8+的适配

从iOS 8.0开始,苹果进一步加强了对用户隐私的保护。
当APP想访问用户的隐私信息时,系统不再自动弹出一个对话框让用户授权

解决方案:

   (1)调用iOS 8.0的API,主动请求用户授权
- (void)requestAlwaysAuthorization  // 请求允许在前后台都能获取用户位置的授权
- (void)requestWhenInUseAuthorization // 请求允许在前台获取用户位置的授权

(2)务必在info.plist文件中配置对应的键值, 否则以上请求授权的方法不生效
NSLocationAlwaysUsageDescription : 允许在前后台获取GPS的描述
NSLocationWhenInUseDescription  : 允许在前台获取GPS的描述

九、iOS 9.0 定位补充

iOS 9.0 如果当前处于前台授权状态,默认是不可以后台获取用户位置。但可以设置以下属性为YES,就可以继续获取后台位置,但是会出现蓝条
@property(assign, nonatomic) BOOL allowsBackgroundLocationUpdates
使用注意:必须设置对应的后台模式:location updates

iOS 9.0 可以单次请求用户位置
- (void)requestLocation  
-(void)locationManager:(nonnull CLLocationManager *)manager didUpdateLocations:(nonnull NSArray *)locations  // 成功调用
-(void)locationManager:(nonnull CLLocationManager *)manager didFailWithError:(nonnull NSError *)error // 失败调用

十、代码示例

1.定位

#import "ViewController.h"
#import 

@interface ViewController ()

/** 位置管理者 */
@property (nonatomic, strong) CLLocationManager *lM;

@end

@implementation ViewController

#pragma mark - 懒加载
- (CLLocationManager *)lM
{
    if (!_lM) {
        // 1. 创建位置管理者
        _lM = [[CLLocationManager alloc] init];
        // 1.1 代理, 通知, block
        _lM.delegate = self;
        
        // 每隔多米定位一次
//        _lM.distanceFilter = 100;
        /**
           kCLLocationAccuracyBestForNavigation // 最适合导航
           kCLLocationAccuracyBest; // 最好的
           kCLLocationAccuracyNearestTenMeters; // 10m
           kCLLocationAccuracyHundredMeters; // 100m
           kCLLocationAccuracyKilometer; // 1000m
           kCLLocationAccuracyThreeKilometers; // 3000m
         */
        // 精确度越高, 越耗电, 定位时间越长
        _lM.desiredAccuracy = kCLLocationAccuracyBest;
        
        
        /** -------iOS8.0+定位适配-------- */
        
        if([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)
        {
            // 前台定位授权(默认情况下,不可以在后台获取位置, 勾选后台模式 location update, 但是 会出现蓝条)
            [_lM requestWhenInUseAuthorization];
            
            
            // 前后台定位授权(请求永久授权)
            // +authorizationStatus != kCLAuthorizationStatusNotDetermined
            // 这个方法不会有效
            // 当前的授权状态为前台授权时,此方法也会有效
//            [_lM requestAlwaysAuthorization];

        }
        // 允许后台获取用户位置(iOS9.0)
         if([[UIDevice currentDevice].systemVersion floatValue] >= 9.0)
         {
             // 一定要勾选后台模式 location updates
             _lM.allowsBackgroundLocationUpdates = YES;
         }
        
//        if ([_lM respondsToSelector:@selector(requestAlwaysAuthorization)])
//        {
//            [_lM requestAlwaysAuthorization];
//        }
        
    }
    return _lM;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    // 2. 使用位置管理者,开始更新用户位置
    // 默认只能在前台获取用户位置,
    // 勾选后台模式 location updates
//    [self.lM startUpdatingLocation];
    
    
    /**
     kCLLocationAccuracyBestForNavigation // 最适合导航
     kCLLocationAccuracyBest; // 最好的
     kCLLocationAccuracyNearestTenMeters; // 10m
     kCLLocationAccuracyHundredMeters; // 100m
     kCLLocationAccuracyKilometer; // 1000m
     kCLLocationAccuracyThreeKilometers; // 3000m
     */
    
//    [self.lM requestLocation];
    
    
    
    
}

#pragma mark - CLLocationManagerDelegate
/**
 *  更新到位置之后调用
 *
 *  @param manager   位置管理者
 *  @param locations 位置数组
 */
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    NSLog(@"定位到了");
    
    // 拿到位置,做一些业务逻辑操作
    
    
    // 停止更新
//    [manager stopUpdatingLocation];
    //    NSLog(@"定位到了");
    /**
     *  CLLocation 详解
     *  coordinate : 经纬度
     *  altitude : 海拔
     *  course : 航向
     *  speed ; 速度
     */

    CLLocation *location = [locations lastObject];
    
//    NSLog(@"%@", location);
    
    /**
     *  场景演示:打印当前用户的行走方向,偏离角度以及对应的行走距离,
        例如:”北偏东 30度  方向,移动了8米”
     */
    
    // 1. 获取方向偏向
    NSString *angleStr = nil;
    
    switch ((int)location.course / 90) {
        case 0:
            angleStr = @"北偏东";
            break;
        case 1:
            angleStr = @"东偏南";
            break;
        case 2:
            angleStr = @"南偏西";
            break;
        case 3:
            angleStr = @"西偏北";
            break;
            
        default:
            angleStr = @"跑沟里去了!!";
            break;
    }

    // 2. 偏向角度
    NSInteger angle = 0;
    angle = (int)location.course % 90;
    
    // 代表正方向
    if (angle == 0) {
        NSRange range = NSMakeRange(0, 1);
        angleStr = [NSString stringWithFormat:@"正%@", [angleStr substringWithRange:range]];
    }
    
    
    // 3.移动多少米
    double distance = 0;
    if(_oldL)
    {
        distance = [location distanceFromLocation:_oldL];
    }
    _oldL = location;
    
    
    
    
    // 4. 拼串 打印
    // 例如:”北偏东 30度  方向,移动了8米”
    NSString *noticeStr = [NSString stringWithFormat:@"%@%zd方向, 移动了%f米", angleStr, angle, distance];
    NSLog(@"%@", noticeStr);
        
}

/**
 *  授权状态发生改变时调用
 *
 *  @param manager 位置管理者
 *  @param status  状态
 */
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
    switch (status) {
            // 用户还未决定
        case kCLAuthorizationStatusNotDetermined:
        {
            NSLog(@"用户还未决定");
            break;
        }
            // 问受限
        case kCLAuthorizationStatusRestricted:
        {
            NSLog(@"访问受限");
            break;
        }
            // 定位关闭时和对此APP授权为never时调用
        case kCLAuthorizationStatusDenied:
        {
            // 定位是否可用(是否支持定位或者定位是否开启)
            if([CLLocationManager locationServicesEnabled])
            {
                NSLog(@"定位开启,但被拒");
            }else
            {
                NSLog(@"定位关闭,不可用");
            }
//            NSLog(@"被拒");
            break;
        }
            // 获取前后台定位授权
        case kCLAuthorizationStatusAuthorizedAlways:
            //        case kCLAuthorizationStatusAuthorized: // 失效,不建议使用
        {
            NSLog(@"获取前后台定位授权");
            break;
        }
            // 获得前台定位授权
        case kCLAuthorizationStatusAuthorizedWhenInUse:
        {
            NSLog(@"获得前台定位授权");
            break;
        }
        default:
            break;
    }

    
    
}

// 定位失败
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    NSLog(@"定位失败");
}

@end

2.指南针

#import "ViewController.h"
#import 


@interface ViewController ()

/** 位置管理者 */
@property (nonatomic, strong) CLLocationManager *lM;

@property (weak, nonatomic) IBOutlet UIImageView *compassView;



@end

@implementation ViewController


- (CLLocationManager *)lM
{
    if (!_lM) {
        _lM = [[CLLocationManager alloc] init];
        _lM.delegate = self;
        
        // 每隔多少度更新一次
        _lM.headingFilter = 2;
        
    }
    return _lM;
}

- (void)viewDidLoad {
    [super viewDidLoad];
   
    
    // 开始监听设备朝向
    [self.lM startUpdatingHeading];
    
}


#pragma mark - CLLocationManagerDelegate
/**
 *  获取到手机朝向时调用
 *
 *  @param manager    位置管理者
 *  @param newHeading 朝向对象
 */
-(void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
    /**
     *  CLHeading 
     *  magneticHeading : 磁北角度
     *  trueHeading : 真北角度
     */
    
    NSLog(@"%f", newHeading.magneticHeading);
    
    CGFloat angle = newHeading.magneticHeading;
    
    // 把角度转弧度
    CGFloat angleR = angle / 180.0 * M_PI;
    
    // 旋转图片
    [UIView animateWithDuration:0.25 animations:^{
        self.compassView.transform = CGAffineTransformMakeRotation(-angleR);
    }];
    
    
}



@end

3.区域监听

#import "ViewController.h"
#import 
@interface ViewController ()

/**  位置管理者 */
@property (nonatomic, strong) CLLocationManager *lM;

@end

@implementation ViewController

- (CLLocationManager *)lM
{
    if (!_lM) {
        _lM = [[CLLocationManager alloc] init];
        _lM.delegate = self;
        
        if([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)
        {
            [_lM requestAlwaysAuthorization];
        }
        
        
    }
    return _lM;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    // 区域监听
    
    CLLocationCoordinate2D center = {21.13, 123.456};
    CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:1000 identifier:@"银江软件园"];
    
//    [self.lM startMonitoringForRegion:region];
    
//    CLLocationCoordinate2D center2 = {21.13, 123.456};
//    CLCircularRegion *region2 = [[CLCircularRegion alloc] initWithCenter:center2 radius:1000 identifier:@"小码哥2"];
//    
//    [self.lM startMonitoringForRegion:region2];

    // 请求区域状态
    [self.lM requestStateForRegion:region];

}


#pragma mark - CLLocationManagerDelegate
// 进入区域
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
    NSLog(@"进入区域--%@", region.identifier);
}

// 离开区域
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
    NSLog(@"离开区域--%@", region.identifier);
}


-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
    NSLog(@"%zd", state);
    
    
}



@end

4.地理编码与反地理编码

#import "ViewController.h"
#import 
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextView *addressTV;
@property (weak, nonatomic) IBOutlet UITextField *laTF;
@property (weak, nonatomic) IBOutlet UITextField *longTF;


/** 地理编码 */
@property (nonatomic, strong) CLGeocoder *geoC;


@end

@implementation ViewController

- (CLGeocoder *)geoC
{
    if (!_geoC) {
        _geoC = [[CLGeocoder alloc] init];
    }
    return _geoC;
}

- (IBAction)geoCoder {
    
    
    NSString *addr  = self.addressTV.text;
    
    if ([addr length] == 0) {
        return;
    }
    
    [self.geoC geocodeAddressString:@"银江软件园" completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
        /**
         *  CLPlacemark
         location : 位置对象
         addressDictionary : 地址字典
         name : 地址全称
         */

        if(error == nil)
        {
            NSLog(@"%@", placemarks);
            [placemarks enumerateObjectsUsingBlock:^(CLPlacemark * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                NSLog(@"%@", obj.name);
                self.addressTV.text = obj.name;
                self.laTF.text = @(obj.location.coordinate.latitude).stringValue;
                self.longTF.text = @(obj.location.coordinate.longitude).stringValue;
            }];
        }else
        {
            NSLog(@"cuowu--%@", error.localizedDescription);
        }
   
    }];
    
}
- (IBAction)reverseGeoCoder {
    
    
    double lati = [self.laTF.text doubleValue];
    double longi = [self.longTF.text doubleValue];
    
    // TODO: 容错
    
    CLLocation *loc = [[CLLocation alloc] initWithLatitude:lati longitude:longi];
    [self.geoC reverseGeocodeLocation:loc completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
        if(error == nil)
        {
            NSLog(@"%@", placemarks);
            [placemarks enumerateObjectsUsingBlock:^(CLPlacemark * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                NSLog(@"%@", obj.name);
                self.addressTV.text = obj.name;
                self.laTF.text = @(obj.location.coordinate.latitude).stringValue;
                self.longTF.text = @(obj.location.coordinate.longitude).stringValue;
            }];
        }else
        {
            NSLog(@"cuowu");
        }
        
    }];
    
    
    
}



@end

你可能感兴趣的:(CoreLocation)