地图系列之一CoreLocation框架
简介
- 在移动互联网时代,移动app能解决用户的很多生活琐事,比如
- 周边:找餐馆、找KTV、找电影院等等
- 导航:根据用户设定的起点和终点,进行路线规划,并指引用户如何到达
- 在上述应用中,都用到了定位和地图功能,在iOS开发中,要想加入这2大功能,必须基于2个框架进行开发
- CoreLocation :用于地理定位,地理编码,区域监听等(着重功能实现)
- MapKit :用于地图展示,例如大头针,路线、覆盖层展示等(着重界面展示)
- 说明: 这两个框架MapKit相对来说更加重量级,MapKit定位是相对于CoreLocation来做的,因此要先研究CoreLocation
- 2个热门专业术语
- LBS :Location Based Service(基于定位的服务)
- SoLoMo :Social Local Mobile(索罗门)
CoreLocation框架
- CoreLocation框架使用前提
- 导入框架(Xcode5.0之后可以省略)
- 导入主头文件
#import <CoreLocation/CoreLocation.h>
- CoreLocation框架使用须知
- CoreLocation框架中所有数据类型的前缀都是CL
- CoreLocation中使用CLLocationManager对象来做用户定位
用户隐私的保护
1. iOS<8.0
- 从iOS 6开始,苹果在保护用户隐私方面做了很大的加强,以下操作都必须经过用户批准授权
- 要想获得用户的位置/想访问用户的通讯录、日历、相机、相册等等
- 当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权
- 一旦用户选择了
Don’t Allow
,意味着你的应用以后就无法使用定位功能
- 开发者必须在Info.plist中设置
NSLocationUsageDescription
说明定位的目的(Privacy - Location Usage Description
) - 前台定位
-
创建位置管理者 CLLocationManager并设置代理
// 创建定位服务管理者 CLLocationManager *localManager = [[CLLocationManager alloc] init]; //设置代理 localManager.delegate = self;
-
开始定位,调用方法,更新位置信息
//开始定位用户位置 [localManager startUpdatingLocation];
-
在CLLocationManagerDelegate的代理方法中获取用户位置信息
/* 只要定位的用户位置就会调用(调用频率特别高) */ - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{ }
-
- 后台定位
- 条件:
- 在前台基础上,勾选后台模式location updates或者直接info.plist文件,添加
Required background modes
(两者实现同一个操作)
- 在前台基础上,勾选后台模式location updates或者直接info.plist文件,添加
- 操作:(两者二选一)
- Capabilities -> Background Models -> 选中Location updates 打钩
-
info.plist操作:
添加Required background modes(数组类型)-> App registers for location updates
- 条件:
2. iOS>=8.0 && iOS<9.0
- 从iOS8.0开始,苹果进一步加强了对用户隐私的保护
- 当APP想访问用户的隐私信息时,系统就不再自动弹出一个对话框让用户授权
- 解决方案:
-
调用iOS8.0的API,主动请求哟公户授权
//请求允许在前台获取用户位置的授权 - (void)requestWhenInUseAuthorization API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(macos); //请求允许在前后台都能获取用户位置的授权 - (void)requestAlwaysAuthorization API_AVAILABLE(ios(8.0), macos(10.15)) API_UNAVAILABLE(tvos);
-
同时务必在info.plist文件中配置对应的键值对,否则以上请求授权方法不生效
NSLocationWhenInUseUsageDescription 允许在前台获取GPS的描述 NSLocationAlwaysUsageDescription 允许在前后台获取GPS的描述
-
- 前台定位
- 创建位置管理者CLLocationManager,并设置代理(略)
- 开始定位,调用方法(略)
- 在CLLocationManagerDelegate的代理方法中获取用户位置信息(略)
- iOS8.0后增加操作:
-
主动请求前台定位授权
[localManager requestWhenInUseAuthorization];
-
在info.plist中填写对应的key(一定要填key)
NSLocationWhenInUseUsageDescription 需要您的同意,才能访问您的位置
-
- 后台定位
- 需要在前台定位基础上,即先将上面4步做完
- 两种方案:
- 方案一:
- 开启后台模式 Location updates
- Capabilities -> Background Models -> 选中Location updates 打钩
- 特点:
- 当在后台获取到用户的位置时,会在顶部显示一个蓝条,提醒用户这个app在不断的获取你的位置信息
- 当用户点击了这个蓝条,会打开对应的app
- 开启后台模式 Location updates
- 方案二:
-
开启前后台定位授权
[localManager requestAlwaysAuthorization];
-
在info.plist中填写对应的key(一定要填key)
NSLocationAlwaysUsageDescription 需要您的同意,才能访问您的位置
-
- 方案一:
3. iOS>+9.0 iOS<11.0
- 前台定位
- 相对于iOS8.0 前台定位一样,没有变化(略)
- 后台定位
- 需要在前台定位基础上,即先将上面4步做完
- 两种方案
- 方案一:
- 开启后台模式 Location updates
- Capabilities -> Background Models -> 选中Location updates 打钩
-
iOS9.0后增加操作
localManager.allowsBackgroundLocationUpdates = YES;
- 特点:
- 当在后台获取到用户的位置时,会在顶部显示一个蓝条,提醒用户这个app在不断的获取你的位置信息
- 当用户点击了这个蓝条,会打开对应的app
- 开启后台模式 Location updates
- 方案二:(与iOS8.0一样)
- 方案一:
4. iOS>= 11.0
-
在之前的基础上在info.plist中增加一个字段即可
NSLocationAlwaysAndWhenInUseUsageDescription 需要您的同意,才能访问您的位置
CLLocationManager
- CLLocationManager可以实现地理定位/指南针/区域监听功能
- 地理定位: 获取当前的位置信息
- 指南针: 获取当前的设备朝向
- 区域监听: 判断某个定位是否在一个区域内
-
CLLocationManager的常用方法
//开始更新用户位置 - (void)startUpdatingLocation; //获取地理位置,只获取一次(iOS>9.0) - (void)requestLocation; //停止更新用户位置 - (void) stopUpdatingLocation; //判断当前应用的定位功能是否可用:为了严谨起见,最好在使用定位功能之前判断当前应用的定位功能是否可用 + (BOOL)locationServicesEnabled;
-
常见属性
每隔多少米定位一次 @property(assign, nonatomic) CLLocationDistance distanceFilter; 定位精确度(越精确就越耗电,定位时间越长) @property(assign, nonatomic) CLLocationAccuracy desiredAccuracy; CLLocationAccuracy常用枚举常量: kCLLocationAccuracyBestForNavigation//最适合导航 kCLLocationAccuracyBest//最好的 kCLLocationAccuracyNearestTenMeters//10m kCLLocationAccuracyHundredMeters//100m kCLLocationAccuracyKilometer//1000m kCLLocationAccuracyThreeKilometers//3000m
-
常用代理方法
/* 当调用了startUpdatingLocation方法后,就开始不断地请求、刷新用户的位置,一旦请求到用户位置就会调用代理的下面方法 只要定位的用户位置就会调用(调用频率特别高) locations: 数组里面是CLLocation对象 */ - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{ NSLog(@"--------%@",locations); //1. 获取位置对象 CLLocation *location = locations.firstObject; //2. 取出经纬度 CLLocationCoordinate2D coordinate = location.coordinate; NSLog(@"纬度为:%f--经度为:-%f",coordinate.latitude,coordinate.longitude); // 停止定位(省电措施:只要不想用定位服务,就马上停止定位服务) [manager stopUpdatingLocation]; } //监听用户授权的状态 - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status; /* CLAuthorizationStatus枚举如下: kCLAuthorizationStatusNotDetermined 用户还未决定 kCLAuthorizationStatusRestricted 访问受限 kCLAuthorizationStatusDenied 用户拒绝 kCLAuthorizationStatusAuthorizedAlways 获得了前后台定位授权 kCLAuthorizationStatusAuthorizedWhenInUse 获得了前台定位授权 */ //获取位置失败调用 - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
CLLocation
- CLLocation用来表示某个位置的地理信息,比如经纬度、海拔等等
-
常用属性
//经纬度 @property(readonly, nonatomic) CLLocationCoordinate2D coordinate; CLLocationCoordinate2D类型是一个结构体: struct CLLocationCoordinate2D { CLLocationDegrees latitude;//纬度 CLLocationDegrees longitude;//经度 }; //通常用CLLocationCoordinate2DMake(CLLocationDegrees latitude, CLLocationDegrees longitude);函数来创建CLLocationCoordinate2D结构体 //海拔 @property(readonly, nonatomic) CLLocationDistance altitude; //路线,航向(取值范围是0.0° ~ 359.9°,0.0°代表真北方向) @property(readonly, nonatomic) CLLocationDirection course; //移动速度(单位是m/s) @property(readonly, nonatomic) CLLocationSpeed speed; //该方法可以计算2个位置之间的距离 - (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
举例使用
地理定位
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<CLLocationManagerDelegate>
@property (nonatomic,strong) CLLocationManager *localManager;
@end
@implementation ViewController
-(CLLocationManager *)localManager{
if (_localManager == nil) {
//1. 创建定位服务管理者
_localManager = [[CLLocationManager alloc] init];;
_localManager.delegate = self;
//每隔多少米定位一次
_localManager.distanceFilter = 100;
//定位精度
_localManager.desiredAccuracy = kCLLocationAccuracyBest;
//iOS8.0+ 前台/前后台定位
[_localManager requestWhenInUseAuthorization];
[_localManager requestAlwaysAuthorization];
}
return _localManager ;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//开始定位用户位置
[self.localManager startUpdatingLocation];
// if (@available(iOS 9.0, *)) {
// [self.localManager requestLocation];
// } else {
// // Fallback on earlier versions
// }
}
/*
只要定位的用户位置就会调用(调用频率特别高)
locations: 数组里面是CLLocation对象
*/
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray<CLLocation *> *)locations{
NSLog(@"--------%@",locations);
//1. 获取位置对象
CLLocation *location = locations.lastObject;
//2. 取出经纬度
CLLocationCoordinate2D coordinate = location.coordinate;
NSLog(@"纬度为:%f--经度为:-%f",coordinate.latitude,coordinate.longitude);
// // 停止定位(省电措施:只要不想用定位服务,就马上停止定位服务)
// [manager stopUpdatingLocation];
}
//检查用户授权的状态
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
}
//定位失败时调用
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
}
@end
指南针
- 思路分析:
- 获取设备朝向
- 磁北方向:
magneticHeading
(常用) - 真北方向:
trueHeading
- 磁北方向:
- 让图片逆向旋转同样的角度
- 注意: 指南针只是获取用户的设备朝向,并没有涉及到用户的隐私,因此不需要用户进行定位授权
#import "ViewController.h" #import <CoreLocation/CoreLocation.h> @interface ViewController ()<CLLocationManagerDelegate> @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property (nonatomic,strong) CLLocationManager *locationManager; @end @implementation ViewController -(CLLocationManager *)locationManager{ if (_locationManager == nil) { _locationManager = [[CLLocationManager alloc] init]; _locationManager.delegate =self; } return _locationManager ; } - (void)viewDidLoad { [super viewDidLoad]; //1. 创建位置管理者 if (CLLocationManager.headingAvailable) { [self.locationManager startUpdatingHeading]; }else{ NSLog(@"当前磁力计设备不支持!!!"); } } - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{ NSLog(@"---------"); //1. 拿到当前设备朝向 //真正的北方: trueHeading //磁北方向 CLLocationDirection angle = newHeading.magneticHeading; //2. 让图片反向旋转 //把角度转换为弧度 CGFloat hudu = angle/180 * M_PI; [UIView animateWithDuration:0.5 animations:^{ self.imageView.transform = CGAffineTransformMakeRotation(-hudu); }]; }
- 获取设备朝向
区域监听
-
使用情况: 设定一个圆形(或其他形状)区域,通过获取用户的位置,当用户进入或者离开该区域时,调用相应的代理方法
#import "ViewController.h" #import <CoreLocation/CoreLocation.h> @interface ViewController ()<CLLocationManagerDelegate> @property (nonatomic,strong) CLLocationManager *locationManager; @end @implementation ViewController -(CLLocationManager *)locationManager{ if (_locationManager == nil) { _locationManager = [[CLLocationManager alloc] init]; _locationManager.delegate =self; //要想进行区域监听,必须进行定位授权 //iOS8.0+ 前台/前后台定位 [_locationManager requestWhenInUseAuthorization]; [_locationManager requestAlwaysAuthorization]; } return _locationManager ; } - (void)viewDidLoad { [super viewDidLoad]; //1. 创建区域 CLLocationCoordinate2D center = CLLocationCoordinate2DMake(22.33, 122.44); CLLocationDistance distance = 1000; if (distance>self.locationManager.maximumRegionMonitoringDistance) { NSLog(@"监听区域超过了最大值"); distance = self.locationManager.maximumRegionMonitoringDistance; } CLRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:distance identifier:@"eeee"]; //class: 监听的区域时什么种类: 圆形(CLCircularRegion)/其他 if ([CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) { //2. 开始监听区域,停止监听:[self.locationManagerstopMonitoringForRegion:region]; [self.locationManager startMonitoringForRegion:region]; //2.1 请求当前区域的状态(程序一启动,先查看当前位置是不是在对应区域内,调用对应的代理方法) [self.locationManager requestStateForRegion:region]; }else{ NSLog(@"不能监听区域"); } } //进入区域时调用 -(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{ NSLog(@"进入了区域"); } //离开区域时调用 -(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{ NSLog(@"离开了区域"); } /* 情况: 因为上面的两个代理方法,只有在动作即区域改变的时候才会调用,但是在有些时候,程序加载之后,我就要展示当前位置是否在区域内,这就需要这个代理方法了 该代理方法跟: [self.locationManager requestStateForRegion:region];结合使用 */ -(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{ switch (state) { case CLRegionStateUnknown: NSLog(@"不知道"); break; case CLRegionStateInside: NSLog(@"在区域内"); break; case CLRegionStateOutside: NSLog(@"在区域外"); break; default: break; } } @end
CLGeocoder
- 使用CLGeocoder可以完成“地理编码”和“反地理编码”
- 地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
- 反地理编码:根据给定的经纬度,获得具体的位置信息
-
地理编码方法
- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
-
反地理编码方法
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;
- CLGeocodeCompletionHandler
-
当地理\反地理编码完成时,就会调用CLGeocodeCompletionHandler这个block
typedef void (^CLGeocodeCompletionHandler)(NSArray *placemarks, NSError *error);
-
参数分析:
- 这个block传递2个参数
- error :当编码出错时(比如编码不出具体的信息)有值
- 里面装着CLPlacemark对象(为什么是一个数组? 因为同一个地名全国可能有几个地方)
-
- 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;
-
代码举例:
#import "ViewController.h" #import <CoreLocation/CoreLocation.h> @interface ViewController () @property (nonatomic,strong) CLGeocoder *geocoder; #pragma mark - 地理编码 @property (weak, nonatomic) IBOutlet UITextField *addressField; @property (weak, nonatomic) IBOutlet UILabel *longitudeLabel; @property (weak, nonatomic) IBOutlet UILabel *latitudeLabel; @property (weak, nonatomic) IBOutlet UILabel *detailAddressLabel; #pragma mark - 反地理编码 @property (weak, nonatomic) IBOutlet UITextField *longtitudeField; @property (weak, nonatomic) IBOutlet UITextField *latitudeField; @property (weak, nonatomic) IBOutlet UILabel *reverseDetailAddressLabel; @end @implementation ViewController -(CLGeocoder *)geocoder{ if (_geocoder == nil) { _geocoder = [[CLGeocoder alloc] init]; } return _geocoder ; } //地理编码 - (IBAction)geocode:(id)sender { if (!self.addressField.text.length) { return; } [self.geocoder geocodeAddressString:self.addressField.text completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) { if (error) {//有错误 NSLog(@""); self.detailAddressLabel.text = @"您查找的地址不存在!"; }else{ CLPlacemark *placemark = placemarks.firstObject; self.longitudeLabel.text = [NSString stringWithFormat:@"%f",placemark.location.coordinate.longitude]; self.latitudeLabel.text = [NSString stringWithFormat:@"%f",placemark.location.coordinate.latitude]; self.detailAddressLabel.text = [NSString stringWithFormat:@"%@-%@-%@",placemark.country,placemark.administrativeArea,placemark.locality]; NSLog(@"%@",placemark.addressDictionary); } }]; } //反地理编码 - (IBAction)reverseGeocode:(id)sender { // 1.包装位置 CLLocationDegrees latitude = [self.latitudeField.text doubleValue]; CLLocationDegrees longitude = [self.longtitudeField.text doubleValue]; CLLocation *loc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude]; [self.geocoder reverseGeocodeLocation:loc completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) { if (error) {//有错误 NSLog(@""); self.reverseDetailAddressLabel.text = @"您查找的地址不存在!"; }else{ CLPlacemark *placemark = placemarks.firstObject; self.reverseDetailAddressLabel.text = [NSString stringWithFormat:@"%@-%@-%@",placemark.country,placemark.administrativeArea,placemark.locality]; NSLog(@"%@",placemark.addressDictionary); } }]; } @end