# CoreLocation **Repository Path**: UIControl_admin/CoreLocation ## Basic Information - **Project Name**: CoreLocation - **Description**: 地图定位 - **Primary Language**: Objective-C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 9 - **Forks**: 0 - **Created**: 2016-05-07 - **Last Updated**: 2025-03-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 定位CoreLocation ####准备内容 地图的开发基于这两个框架 ① CoreLocation:用户地理定位,地理编码,区域监听等(着重功能实现) ② MapKit:用于地图展示,例如大头针,路线,覆盖层展示等(着重界面展示) 区域监听:在某个范围内,可以检测用户的进入和离开 #### 热门的专业术语 LBS:Location Based Service(基于位置的服务) SoLoMo:Social Local Mobile(索罗门:社交 本地 移动 ) 地理编码:将地址转换为经纬度 反地理编码:将经纬度转换为地址 ####CoreLocation使用 ① 导入框架CoreLocation.framework (Xcode5 以后可以省略) ② 导入主头文件 #import 注意 ① CoreLocation框架中的所有数据类型的前缀都是CL ② CoreLocation中使用CLLocationManager对象来做用户定位 ####Demo1知识点 CoreLocation框架的基本使用-定位.(iOS8.0-) 1> iOS8.0的定位实现 2> 设置授权说明 3> 设置位置更新的距离过滤(防止过于频繁的调用代理方法) 4> 设置定位精度(精度越高,耗电越快,所以要根据需要选择合适的定位精度) 5> 后台定位(勾选后台模式:location update) ####Demo2知识点 CoreLocation框架的基本使用-定位.(iOS8.0+适配) 1> iOS8.0+授权适配(两种适配方案:通过系统版本号,通过对象是否响应方法)(配置info.plist文件中对应的键值) 2> requestWhenInUseAuthorization 和 requestAlwaysAuthorization 区别(前者只有在APP前台时可以定位,后者可以在前后台进行定位) 3> 勾选后台运行模式location后的变化(在iOS9之前,前者后台依然可以定位,但是会出现蓝条;后者不会出现蓝条;) 4> 授权状态的变更,调用对应的代理方法(说明不同状态代表的含义,给予用户对应的提示) 5> 演示前后台授权和前台授权同时请求发生什么情况,并解释原因 ####Demo3知识点 CoreLocation框架的基本使用-定位.(iOS9.0 补充) 1> requestWhenInUseAuthorization 和 requestAlwaysAuthorization 区别(前者只有在APP前台时可以定位,后者可以在前后台进行定位) 2> 勾选后台运行模式location后的变化(效果同上,在while using the app授权模式下,要想再次使用后台定位,必须使用allowsBackgroundLocationUpdates方法进行设置,但同样还是会出现蓝条) 3> requestLocation 作用:按照定位精确度从低到高进行排序,逐个进行定位.如果获取到的位置不是精确度最高的那个,也会在定位超时后,通过代理告诉外界(必须实现代理的-locationManager:didFailWithError:方法,不能与startUpdatingLocation方法同时使用) ####Demo4知识点 1> coordinate 当前位置所在的经纬度 2> altitude 海拔 3> speed 当前速度 4> distanceFromLocation 获取两个位置之间的直线物理距离 应用场景:用户在行走的时候告诉他行走方向,偏离角度,对应的行走距离. 例如:北偏东 30度方向 移动了8米 ####Demo5知识点 1> 指南针效果 ① 获取手机设备朝向(距离真北方向的角度) ② 让指南针图片反向旋转对应角度 2> 获取手机朝向 [location startUpdatingHeading]; 3> magneticHead (磁北方向和真北方向:取值范围0-359.9 顺时针为正) 注意:获取手机设备朝向不需要用户定位授权 ####Demo6知识点 1> startMOnitoringForRegion (CLCorcilarRegion区域;注意,因为需要使用到用户的当前位置,所以iOS8.0+后需要请求用户授权) 2> locationManager:didEnterRegion: 进入区域 3> locationManager:didExitRegion: 离开区域 4> 后台区域监听 ####Demo7知识点(CLGeocoder) 地理编码:根据给定的位置(通常是地名)确定地理坐标(经、纬度) 反地理编码:可以根据地理坐标(经、纬度)确定位置信息(街道、门牌等) 演示效果 1> 三种编码方案 2> CLPlacemark讲解(locality:城市名称 thoroughfare:接到名称 name:全称) 3> 反地理编码 补充:百度地图的拾取器 http://s.suixingpay.com/static/baiduMap/baiduMap.jsp 芜湖通全科技创业园 地址:银湖北路50 坐标:118.380427,31.403001 ####Demo8知识点 CoreLocation第三方框架 -- locationManager github地址:https://github.com/intuit/LocationManager pod配置:pod 'INTULocationManager' 注:系统自带的定位需要通过delegate方法来实现,用法很复杂;该第三方库直接用block来实现 强调:该第三方对于iOS9.0 ,前台授权时,后台是无法获取用户位置 补充:delayUntilAuthorized:YES表示什么意思 INTULocationManager *locMgr = [INTULocationManager sharedInstance]; [locMgr requestLocationWithDesiredAccuracy:INTULocationAccuracyCity timeout:10.0 delayUntilAuthorized:YES block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) { if (status == INTULocationStatusSuccess) { } else if (status == INTULocationStatusTimedOut) { } else { } }]; INTULocationManager *locMgr = [INTULocationManager sharedInstance]; [locMgr subscribeToLocationUpdatesWithDesiredAccuracy:INTULocationAccuracyRoom block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) { if (status == INTULocationStatusSuccess) { // A new updated location is available in currentLocation, and achievedAccuracy indicates how accurate this particular location is. NSLog(@"OK--%@", currentLocation); } else { // An error occurred, more info is available by looking at the specific status returned. The subscription has been kept alive. } }]; ####补充10(懒加载:重写属性的get方法) 1. 防止对象被提前创建(节省内存:能不创建就不创建) 2. 防止对象重复创建(只创建一次) 3. 防止对象使用时还没有被创建(随时都可以通过self打点调用) 4. 可以在懒加载方法中进行初始化操作(代码更集中,方便管理,耦合性降低) ---- ####Demo1代码(以下代码只对iOS8之前有效) /** 基本用法 * ① 创建位置管理者 * ② 使用位置管理者,开始更新用户位置 */ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { CLLocationManager *manager = [[CLLocationManager alloc] init]; // 定位成功后如何告诉别人? // 代理 通知 block manager.delegate = self; [manager startUpdatingLocation]; } #pragma mark CLLocationManagerDelegate - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { NSLog(@"定位成功"); } 说明:点击空白地方以后代理方法不调用. 原因:在ARC下,局部变量出了作用域就会被释放,因此Manager已经被释放并置空了,因此delegate没反应 改进:将manager声明为属性,用懒加载初始化 ---- @property (nonatomic, strong) CLLocationManager *manager; - (CLLocationManager *)manager { if(!_manager) { _manager = [[CLLocationManager alloc] init]; // 每隔多远定位一次 // 通过手动修改经纬度坐标可以验证 _manager.distanceFilter = 100; /** * 精确度 * 最适合导航 * kCLLocationAccuracyBestForNavigation * 最好的 * kCLLocationAccuracyBest; * 10米 * kCLLocationAccuracyNearestTenMeters; * 100米 * kCLLocationAccuracyHundredMeters; * 1000米 * kCLLocationAccuracyKilometer; * 3000米 * kCLLocationAccuracyThreeKilometers; * 说明:精确度越高越费电,定位时间越长,具体看需求 * 例如: * 查看自己所在城市:3000米 * 导航去火车站:10米 * 注意:退到后台后定位的代理方法不执行. * Targets -> Capbilities -> Background Modes 开关打开 -> 打开Location Updates * 本质就是在plist中添加一个字段而已 */ _manager.desiredAccuracy = kCLLocationAccuracyBest; _manager.delegate = self; } return _manager; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.manager startUpdatingLocation]; } #pragma mark CLLocationManagerDelegate - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { NSLog(@"定位成功"); // 该方法会一直调用 // 拿到位置,会做一些业务逻辑操作 // 功能1:定位成功一次后就停止定位 // 功能2:导航功能.每个10米就定位一次 [manager stopUpdatingLocation]; } ---- 注: 用户隐私的保护 从iOS6开始,苹果在保护用户隐私方面做了很大的加强,以下操作都必须经过用户批准授权. > 想要获得用户的位置 > 想访问用户的通讯录,日历,相机,相册等等 当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权. 注:用户可能点击OK,也有可能点击Don't Allow.如果用户拒绝的话,会造成不小的麻烦. 为了解决这个问题,可以在Info.plist中设置NSLocationUsageDescription说明定位的目的(字段:Privacy - Location Usage Description)(该字段只适用于iOS8.0以前) ####Demo2代码 从iOS8.0开始,苹果进一步加强了对用户隐私的保护. 当APP想访问用户的隐私信息时,系统不再自动弹出一个对话框让用户授权 解决方案: 1> 调用iOS8.0的API,主动请求用户授权 * - (void)requestAlwaysAuthorization // 请求允许在前后台都能获取用户位置的授权 * - (void)requestWhenInUseAuthorization // 请求允许在前台获取用户位置的授权 --- - (CLLocationManager *)manager { if(!_manager) { _manager = [[CLLocationManager alloc] init]; _manager.distanceFilter = 100; _manager.desiredAccuracy = kCLLocationAccuracyBest; _manager.delegate = self; /** ------------iOS 8.0+ 定位适配---------------------------- * 注意: 这个方法需要手动在plist添加一个字段.详情看官方注释 * 注:如果点击运行Allow后没有调用delegate方法的话可能是模拟器问题,重置重新运行即可 * 前台定位授权(默认情况下不可以在后台获取位置,勾选后台模式 location update.但是到后台后上方会有蓝条) */ [_manager requestWhenInUseAuthorization]; /** ------------iOS 8.0+ 定位适配---------------------------- * 前后台定位授权(请求永久授权,没有蓝条,不勾选后台模式也可以) */ [_manager requestAlwaysAuthorization]; } return _manager; } 说明:这么写的前提条件是iOS8以后,如果在iOS7上运行,它会直接崩溃,因此需要适配 --- - (CLLocationManager *)manager { if(!_manager) { _manager = [[CLLocationManager alloc] init]; _manager.distanceFilter = 100; _manager.desiredAccuracy = kCLLocationAccuracyBest; _manager.delegate = self; /** * 适配方案①:通过判断版本号来适配 */ if ([[UIDevice currentDevice].systemName floatValue] > 8.0) { //[_manager requestWhenInUseAuthorization]; [_manager requestAlwaysAuthorization]; } /** * 适配方法②:通过判断是否能响应方法来适配 */ /*if ([_manager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { }*/ if ([_manager respondsToSelector:@selector(requestAlwaysAuthorization)]) { } } return _manager; } 说明:每次重新运行程序,都要申请授权,没有必要!请查看其官方注释 * When +authorizationStatus != kCLAuthorizationStatusNotDetermined, (ie * generally after the first call) this method will do nothing. 注意写法:如果两句授权都写上,会出现什么结果? 1> 先requestAlwaysAuthorization,写requestWhenInUseAuthorization 效果:只出现一次 2> 先写requestWhenInUseAuthorization,后requestAlwaysAuthorization 效果:第一次一次,第二次一次.总共两次 --- /** * 授权状态发生改变时调用 * 应该根据用户不同的设置提示他对应的内容 */ - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { switch (status) { case kCLAuthorizationStatusNotDetermined: { NSLog(@"用户还未决定"); } break; case kCLAuthorizationStatusRestricted: { NSLog(@"访问受限"); } break; case kCLAuthorizationStatusDenied: { NSLog(@"定位关闭时和对此APP授权为never时调用"); // 定位是否可用(是否支持定位或者定位是否开启) if([CLLocationManager locationServicesEnabled]) { NSLog(@"定位开启,但被拒"); } else { NSLog(@"定位关闭,不可用"); } } break; case kCLAuthorizationStatusAuthorizedAlways: { // case kCLAuthorizationStatusAuthorized: // 失效,不建议使用 NSLog(@"获取前后台定位授权"); } break; case kCLAuthorizationStatusAuthorizedWhenInUse: { NSLog(@"获得前台定位授权"); } break; default: break; } } ####Demo3代码 - (CLLocationManager *)manager { if(!_manager) { _manager = [[CLLocationManager alloc] init]; _manager.distanceFilter = 100; _manager.desiredAccuracy = kCLLocationAccuracyBest; _manager.delegate = self; if ([_manager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { [_manager requestWhenInUseAuthorization]; } /** * iOS9.0的新变化 * 在前台模式的授权状态下,也想后台定位需要设置下面的属性,而且必须勾选后台模式,否则会崩溃(闪退) */ if ([_manager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) { _manager.allowsBackgroundLocationUpdates = YES; } } return _manager; } ####Demo4代码 - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { NSLog(@"定位成功"); // [manager stopUpdatingLocation]; // chronological:按时间排序 CLLocation *location = [locations lastObject]; // 经纬度(coordinate): latitude纬度 longitude经度 // 海拔(altitude): // 楼层(floor) // course:航向 // speed:速度 NSLog(@"%@", location); // ① 行走方向 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; } // ② 偏离角度 NSInteger angle = 0; angle = (int)location.course%90; // 根据角度获取正南正北正东正西 if (angle == 0) { NSRange range = NSMakeRange(0, 1); angleStr = [NSString stringWithFormat:@"%@", [angleStr substringWithRange:range]]; } // ③ 移动多少米 CGFloat distance = 0; if (_oldL) { distance = [location distanceFromLocation:_oldL]; } _oldL = location; // ④ 打印信息 self.lb.text = [NSString stringWithFormat:@"前进方向:%@,偏离角度:%@,移动距离:%.2f", angleStr,@(angle),distance]; } // 单位:meters CLLocation *l1 = [[CLLocation alloc] initWithLatitude:21.123 longitude:123.456]; CLLocation *l2 = [[CLLocation alloc] initWithLatitude:22.123 longitude:123.456]; CLLocationDistance distance = [l1 distanceFromLocation:l2]; NSLog(@"%f", distance); ####Demo5代码 注:导航一般使用磁北方向 #import "ViewController.h" #import @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imgV; @property (nonatomic, strong) CLLocationManager *manager; @end @implementation ViewController - (CLLocationManager *)manager { if(!_manager) { _manager = [[CLLocationManager alloc] init]; // 每隔多少度更新一次 _manager.headingFilter = 10; _manager.delegate = self; } return _manager; } - (void)viewDidLoad { [super viewDidLoad]; // 开始监听设备朝向 [self.manager startUpdatingHeading]; } - (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading { NSLog(@"获取到手机朝向 -- %@", newHeading); /** * 磁北角度:magneticHeading * 真北角度:trueHeading */ NSLog(@"磁北:%f\n真北:%f", newHeading.magneticHeading, newHeading.trueHeading); /** * 旋转图片 * 写法 ①:有卡顿 * self.imgV.transform = CGAffineTransformMakeRotation(-newHeading.magneticHeading*M_PI/180); * 写法 ②:加动画 */ [UIView animateWithDuration:0.25 animations:^{ self.imgV.transform = CGAffineTransformMakeRotation(-newHeading.magneticHeading*M_PI/180); }]; } @end ####Demo6代码 #import "ViewController.h" #import @interface ViewController () /** 定位管理器 */ @property (nonatomic, strong) CLLocationManager *manager; @end @implementation ViewController - (CLLocationManager *)manager { if(!_manager) { _manager = [[CLLocationManager alloc] init]; _manager.delegate = self; if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) { [_manager requestAlwaysAuthorization]; } } return _manager; } - (void)viewDidLoad { [super viewDidLoad]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 已经被废弃 //CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:<#(CLLocationCoordinate2D)#> radius:<#(CLLocationDistance)#> identifier:<#(nonnull NSString *)#>]; // 细节 // 结构体的创建形式 // CGRect rect = CGRectMake(0, 0, 100, 100); // CGRect rect1 = {0, 0, 100, 100}; CLLocationCoordinate2D point = {21.13,123.456}; CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:point radius:300 identifier:@"区域一"]; [self.manager startMonitoringForRegion:region]; CLLocationCoordinate2D point1 = {21.13,123.456}; CLCircularRegion *region1 = [[CLCircularRegion alloc] initWithCenter:point1 radius:300 identifier:@"区域二"]; [self.manager startMonitoringForRegion:region1]; /** * 补充 * 可以主动请求某个区域的状态,当进入/离开区域时也会调用对应的代理方法 * 它对应的代理方法: * locationManager:didDetermineState:forRegion: */ [self.manager requestStateForRegion:region]; } // 进入区域 - (void)locationManager:(CLLocationManager *)manager didEnterRegion:(nonnull CLRegion *)region { /** * 思考:如果要同时监听多个区域,怎么实现? * 通过判断标识符来实现:region.identifier */ NSLog(@"进入区域"); } - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region { NSLog(@"离开区域"); } - (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region { /** * state * * CLRegionStateUnknown, * CLRegionStateInside, * CLRegionStateOutside */ } @end ####Demo7代码 - (void)viewDidLoad { [super viewDidLoad]; [self getCoordinateByAddress:@"北京"]; [self getAddressByLatitude:39.54 longitude:116.28]; } #pragma mark 根据地名确定地理坐标 -(void)getCoordinateByAddress:(NSString *)address{ //地理编码 [self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) { //取得第一个地标,地标中存储了详细的地址信息,注意:一个地名可能搜索出多个地址 CLPlacemark *placemark=[placemarks firstObject]; CLLocation *location=placemark.location;//位置 CLRegion *region=placemark.region;//区域 NSDictionary *addressDic= placemark.addressDictionary;//详细地址信息字典,包含以下部分信息 // NSString *name=placemark.name;//地名 // NSString *thoroughfare=placemark.thoroughfare;//街道 // NSString *subThoroughfare=placemark.subThoroughfare; //街道相关信息,例如门牌等 // NSString *locality=placemark.locality; // 城市 // NSString *subLocality=placemark.subLocality; // 城市相关信息,例如标志性建筑 // NSString *administrativeArea=placemark.administrativeArea; // 州 // NSString *subAdministrativeArea=placemark.subAdministrativeArea; //其他行政区域信息 // NSString *postalCode=placemark.postalCode; //邮编 // NSString *ISOcountryCode=placemark.ISOcountryCode; //国家编码 // NSString *country=placemark.country; //国家 // NSString *inlandWater=placemark.inlandWater; //水源、湖泊 // NSString *ocean=placemark.ocean; // 海洋 // NSArray *areasOfInterest=placemark.areasOfInterest; //关联的或利益相关的地标 NSLog(@"位置:%@,区域:%@,详细信息:%@",location,region,addressDic); }]; } #pragma mark 根据坐标取得地名 -(void)getAddressByLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude{ //反地理编码 CLLocation *location=[[CLLocation alloc]initWithLatitude:latitude longitude:longitude]; [self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *placemark=[placemarks firstObject]; NSLog(@"详细信息:%@",placemark.addressDictionary); }]; } @end