标签:
现在很多社交、电商、团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的。的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式。例如你到了一个陌生的地方想要查找附近的酒店、超市等就可以打开软件搜索周边;类似的,还有很多团购软件可以根据你所在的位置自动为你推荐某些商品。总之,目前地图和定位功能已经大量引入到应用开发中。在产品研发中有两个专业术语需要大家知道:一是LBS(Location Based Service)基于定位的服务,二是SoLoMo(Social Local Mobile )社交本地的移动应用,这都是需要我们提供地图和定位服务。
在后面的课程里就和大家一起看一下iOS如何进行地图和定位开发。iOS系统为了方便我们开发,提供了地图服务的框架。除此之外,实际的开发工作中,我们常常会使用一些第三发的SDK来实现地图服务,主要有高德地图和百度地图,这些我们后在后面的几节中讲到。
地图功能的实现离不开定位服务,下面我们还是先来看一下iOS系统的定位功能是如何实现的。
要实现地图、导航功能,往往需要先熟悉定位功能,iOS中的定位引擎是CoreLocation框架提供的,我们通过CoreLocation框架进行定位操作。CoreLocation自身可以单独使用,和地图开发框架MapKit完全是独立的,但是往往地图开发要配合定位框架使用。在Core Location中主要包含了定位、地理编码(包括反编码)功能。
我们先介绍一下iOS定位功能的实现。定位是一个很常用的功能,如一些地图软件打开之后如果用户允许软件定位的话,那么打开软件后就会自动锁定到当前位置,如果用户手机移动那么当前位置也会跟随着变化。要实现这个功能需要使用CoreLoaction中CLLocationManager类,首先看一下这个类的一些主要方法和属性:
类方法
说明
是否启用定位服务,通常如果用户没有启用定位服务可以提示用户打开定位服务
定位服务授权状态,返回枚举类型:
kCLAuthorizationStatusNotDetermined: 用户尚未做出决定是否启用定位服务
kCLAuthorizationStatusRestricted: 没有获得用户授权使用定位服务,可能用户没有自己禁止访问授权
kCLAuthorizationStatusDenied :用户已经明确禁止应用使用定位服务或者当前系统定位服务处于关闭状态
kCLAuthorizationStatusAuthorizedAlways: 应用获得授权可以一直使用定位服务,即使应用不在使用状态
kCLAuthorizationStatusAuthorizedWhenInUse: 使用此应用过程中允许访问定位服务
属性 | 说明 |
---|---|
desiredAccuracy | 定位精度,枚举类型:kCLLocationAccuracyBest:最精确定位CLLocationAccuracy kCLLocationAccuracyNearestTenMeters:十米误差范围kCLLocationAccuracyHundredMeters:百米误差范围kCLLocationAccuracyKilometer:千米误差范围kCLLocationAccuracyThreeKilometers:三千米误差范围 |
distanceFilter | 位置信息更新最小距离,只有移动大于这个距离才更新位置信息,默认为kCLDistanceFilterNone:不进行距离限制 |
对象方法 | 说明 |
startUpdatingLocation | 开始定位追踪,开始定位后将按照用户设置的更新频率执行-(void)locationManager:(CLLocationManager*)manager didUpdateLocations:(NSArray *)locations;方法反馈定位信息 |
stopUpdatingLocation | 停止定位追踪 |
startUpdatingHeading | 开始导航方向追踪 |
stopUpdatingHeading | 停止导航方向追踪 |
startMonitoringForRegion: | 开始对某个区域进行定位追踪,开始对某个区域进行定位后。如果用户进入或者走出某个区域会调用- (void)locationManager:(CLLocationManager )manager didEnterRegion:(CLRegion )region和- (void)locationManager:(CLLocationManager )manager didExitRegion:(CLRegion )region代理方法反馈相关信息 |
stopMonitoringForRegion: | 对某个区域进行定位追踪 |
requestWhenInUseAuthorization | 请求获得应用使用时的定位服务授权,注意使用此方法前在要在info.plist中配置NSLocationWhenInUseUsageDescription |
requestAlwaysAuthorization | 请求获得应用一直使用定位服务授权,注意使用此方法前要在info.plist中配置NSLocationAlwaysUsageDescription |
代理方法 | 说明 |
-(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray )locations; | 位置发生改变后执行(第一次定位到某个位置之后也会执行) |
-(void)locationManager:(CLLocationManager )manager didUpdateHeading:(CLHeading )newHeading; | 导航方向发生变化后执行 |
-(void)locationManager:(CLLocationManager )manager didEnterRegion:(CLRegion )region | 进入某个区域之后执行 |
-(void)locationManager:(CLLocationManager )manager didExitRegion:(CLRegion )region | 走出某个区域之后执行 |
除了CLLocationManager之外,CLLocation类也是在我们做定位服务中经常看到的,CLLocation常用来表示某个位置的地理信息,比如经纬度、海拔高度等,当然他也给我们提供了计算两个地理位置之间间距的方法。下面我们看一下CLLocation的常用属性和方法:
方法 | 说明 |
---|---|
-(instancetype)initWithLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude; | 初始化CLLocation,传入经纬度 |
-(instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate altitude:(CLLocationDistance)altitude horizontalAccuracy:(CLLocationAccuracy)hAccuracy verticalAccuracy:(CLLocationAccuracy)vAccuracy timestamp:(NSDate *)timestamp | 初始化CLLocation,传入经纬度、定位精度和定位时间 |
-(instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate altitude:(CLLocationDistance)altitude horizontalAccuracy:(CLLocationAccuracy)hAccuracy verticalAccuracy:(CLLocationAccuracy)vAccuracy course:(CLLocationDirection)course speed:(CLLocationSpeed)speed timestamp:(NSDate *)timestamp; | 初始化CLLocation,传入经纬度、定位精度和定位时间、定位路线、当前速度 |
-(CLLocationDistance) getDistanceFrom:(const CLLocation *); | 计算两个点之间的距离 |
-(CLLocationDistance) distanceFromLocation:(const CLLocation *) | 计算两个点之间的距离 |
属性 | 说明 |
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate; | 经纬度,结构体,一般通过函数CLLocationCoordinate2DMake来创建 |
@property(readonly, nonatomic) CLLocationDistance altitude; | 海拔 |
@property(readonly, nonatomic) CLLocationAccuracy horizontalAccuracy; | 水平定位精度 |
@property(readonly, nonatomic) CLLocationAccuracy verticalAccuracy; | 竖直定位精度 |
@property(readonly, nonatomic) CLLocationDirection course; | 路线,航线(范围在0-359.9之间,0代表正北方向) |
@property(readonly, nonatomic) CLLocationSpeed speed; | 行走速度(m/s) |
@property(readonly, nonatomic, copy) NSDate *timestamp; | 时间标记 |
一般在开始定位之前,应用会向用户获取授权请求,在iOS7及以前的版本,如果在应用程序中使用定位服务只要在程序中调用startUpdatingLocation方法应用就会询问用户是否允许此应用是否允许使用定位服务,同时在提示过程中可以通过在info.plist中配置通过配置Privacy -Location Usage Description告诉用户使用的目的,当然这个配置是可选的。但是在iOS8中配置配置项发生了变化,我们可以通过配置NSLocationAlwaysUsageDescription或者 NSLocationWhenInUseUsageDescription来告诉用户使用定位服务的目的,并且注意这个配置是必须的,如果不进行配置则默认情况下应用无法使用定位服务,打开应用不会给出打开定位服务的提示,除非安装后自己设置此应用的定位服务。同时,在应用程序中需要根据配置对requestAlwaysAuthorization或locationServicesEnabled方法进行请求。
iOS8提供了更加人性化的定位服务选项。应用的定位服务不再仅仅是关闭或打开。现在,定位服务的启用提供了三个选项:永不、使用应用程序期间、和始终。同时,考虑到能耗问题,如果一款 App 要求始终能在后台开启定位服务,iOS 8 不仅会在首次打开 App 时主动向你询问,还会在日常使用中弹窗提醒你该 App 一直在后台使用定位服务,并询问你是否继续允许。
下面我们就来看一看iOS实现定位服务的具体步骤有哪些
示例代码
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()<CLLocationManagerDelegate>
// 定位服务管家
@property (nonatomic, strong)CLLocationManager *locationManager;
@end
@implementation ViewController
/*定位的实现
1.导入框架 CoreLocation.framework
2.导入库文件 #import <CoreLocation/CoreLocation.h>
3.需要将定位管家 CLLocationManager 设置为全区变量
1.判断硬件是否开启了定位服务
2.初始化定位管家的对象,注意需要设置为全局变量
3.判断定位服务授权状态,设置定位权限:iOS8的新特性,可以实现代理方法获取授权范围。注意需要修改plist文件 NSLocationAlwaysUsageDescription/NSLocationWhenInUseUsageDescription。
4.设置定位服务的属性。
5.开启定位,实现代理方法,获取定位信息
*/
- (void)viewDidLoad {
[super viewDidLoad];
// 1.判断硬件是否开启了定位服务
BOOL isOpen = [CLLocationManager locationServicesEnabled];
if (isOpen) {
NSLog(@"定位服务已经打开");
}else {
NSLog(@"定位服务未开启");
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"你的定位服务未开启" message:@"请到setting开启定位服务" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
return;
}
// 判断应用是否获取定位授权
NSInteger status = [CLLocationManager authorizationStatus];
switch (status) {
case 0:
NSLog(@"kCLAuthorizationStatusNotDetermined--没有决定");// 用户从未选择过权限
break;
case 1:
NSLog(@"kCLAuthorizationStatusRestricted--没有许可");// 无法使用定位服务,该状态用户无法改变
break;
case 2:
NSLog(@"kCLAuthorizationStatusDenied--禁止使用");// 用户拒绝该应用使用定位服务,或是定位服务总开关处于关闭状态
break;
case 3:
NSLog(@"kCLAuthorizationStatusAuthorizedAlways--始终允许");// 大致是用户同意程序在任意时候使用地理位置
break;
case 4:
NSLog(@"kCLAuthorizationStatusAuthorizedWhenInUse--开启允许");// 大致是用户同意程序在可见时使用地理位置
break;
default:
break;
}
// 2.初始化定位管家的对象,注意需要设置为全局变量,因为我们需要一直持有定位管家的对象,局部变量使用后即被销毁,在代理方法中,无法获得该对象及其属性,所以需要设置为全局变量
[self locationManager];
// 3.判断定位服务授权状态,设置定位权限:iOS8的新特性,可以实现代理方法获取授权范围。注意需要修改plist文件NSLocationAlwaysUsageDescription/NSLocationWhenInUseUsageDescription。
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
// 判断授权状态,在授权未确定是获取授权,一旦确定即无法在程序中修改,只能在settings中对app做授权设置
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {// 判断系统版本 大于8.0以上的版本可以手动获取授权
// 请求始终允许访问,包括进入后台后 对应info设置:NSLocationAlwaysUsageDescription
// [self.locationManager requestAlwaysAuthorization];
// 请求当app打开时允许访问 对应info设置:NSLocationWhenInUseUsageDescription
[self.locationManager requestWhenInUseAuthorization];
// 注意:修改plist文件
// NSLocationAlwaysUsageDescription---我想在后台还访问您的位置
// NSLocationWhenInUseUsageDescription---我想在我的app开启的时候使用您的位置,可以吗?
}
}else if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"你没有给该应用的定位服务授权" message:@"请到setting设置定位服务授权" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
return;// 没有给予授权
}
// 4.设置定位服务的属性。
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
// 设置定位刷新距离,可以直接是由上面的参数指定
[self.locationManager setDistanceFilter:100];//移动距离大于distanceFilter就会定位,否则不会,避免频繁的定位,消耗电量
// 5.开启定位,实现代理方法,获取定位信息
[self.locationManager startUpdatingLocation];
// [self.locationManager stopUpdatingLocation];// 获取定位数据后调用
// 开始追踪导航方向,
[self.locationManager startUpdatingHeading];
// [self.locationManager stopUpdatingHeading];// 停止追踪导航方向
// 开启区域追踪,需要传入一个追踪区域
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(39.0, 116.0);
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:1000 identifier:@"找到你了"];
[self.locationManager startMonitoringForRegion:region];
}
// 懒加载定位管家
- (CLLocationManager *)locationManager {
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;// 设置定位服务的代理对象
}
return _locationManager;
}
#pragma mark CLLocationManagerDelegate
/**
* 方法说明:当用户许可状态发生改变时,调用该方法
*
* @param status:用户的许可状态
*
* @return
*/
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
NSLog(@"认证状态改变 status:%d",status);
}
/**
* 方法说明:执行定位后调用该方法
*
* @param
*
* @return
*/
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations {
// 停止定位(省电措施:只要不想用定位服务,或者获取定位信息后,就马上停止定位服务)
[self.locationManager stopUpdatingLocation];
// 1.取出位置对象(数组中可能会有多个位置对象,取出第一个是最精确的)
CLLocation *loc = [locations firstObject];
// 2.取出经纬度 coordinate:位置坐标 course:方向
CLLocationCoordinate2D coordinate = loc.coordinate;
CLLocationDegrees longitude = coordinate.longitude;
CLLocationDegrees latitude = coordinate.latitude;
// 3.CLLocations的常用属性和方法
/*
horizontalAccuracy,用来得到水平上的精确度,它的大小就是定位精度的半径,单位为米。获得的不是用户设置的精度 而是最终定位的精度,如果值为-1,则说明此定位不可信。
course 方向: 0 ~ 359.9 , 0 代表正北
speed 速度:m/s
获取两个位置之间的距离
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
*/
NSLog(@"经度:%f \n 纬度:%f", longitude, latitude);
NSLog(@"方向:%f \n 海拔:%f",loc.course, loc.altitude);
NSLog(@"水平定位精度%f 竖直定位精度%f",loc.horizontalAccuracy,loc.verticalAccuracy);
NSLog(@"速度:%f",loc.speed);
// 计算2个经纬度之间的直线距离
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:40 longitude:116];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:41 longitude:116];
// 计算2个经纬度之间的直线距离
CGFloat distance = [self countLineDistance:loc1 withLocation:loc2];
NSLog(@"%f",distance);
}
/**
* 计算2个经纬度之间的直线距离
*/
- (double)countLineDistance:(CLLocation *)loc1 withLocation:(CLLocation *)loc2
{
CLLocationDistance distance = [loc1 distanceFromLocation:loc2];
return distance;
}
/**
* 方法说明: 导航方向发生变化的时候执行此方法
*
* @param newHeading 方向: 0 ~ 359.9 , 0 代表正北
*
* @return
*/
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
NSLog(@"方向改变");
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(@"进入到该区域");
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
NSLog(@"离开该区域");
}
@end
使用定位功能时,有几点需要我们注意:
除了提供位置跟踪功能之外,在定位服务中还包含CLGeocoder类用于处理地理编码和逆地理编码功能。这个功能的实现主要是由CLGeocoder类提供的。CLGeocoder最主要的两个方法就是
在上面两个方法的方法中,当地理编码或是反编码结束时,会回调CLGeocodeCompletionHandler,当中传递给我们两个参数,一个NSArray,一个NSError。NSError是编码或者反编码错误的详情,数组中则存放着我们地理编码或是反地理编码后获取的地标信息,为什么是数组呢,因为我们可能查询到多个地标,比如“帝都”代表的地标就有多个。在CoreLocation框架中,地标是一个CLPlacemark的实例对象。下面我们先来看一下CLPlacemark的常用属性:
属性 | 说明 |
---|---|
@property (nonatomic, readonly, copy, nullable) CLLocation *location; | 地理坐标 |
@property (nonatomic, readonly, copy, nullable) CLRegion *region; | 地理范围 |
@property (nonatomic, readonly, copy, nullable) NSTimeZone *timeZone | 地标所在时区 |
@property (nonatomic, readonly, copy, nullable) NSString *name; | 地名 |
@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; | 邮编 |
@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 *areasOfInterest; | 关联的或利益相关的地标 |
@property (nonatomic, readonly, copy) NSDictionary *addressDictionary; | 地理的详细信息 包含上面所有的数据 |
示例代码
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()
@property (nonatomic, strong)CLGeocoder *geocoder;//地理编码器
#pragma mark - 地理编码 根据地区名称查询所在的经纬度坐标
@property (strong, nonatomic) IBOutlet UITextField *addressField;//输入地区名称
@property (strong, nonatomic) IBOutlet UILabel *longitudeLabel;//显示地区的经度
@property (strong, nonatomic) IBOutlet UILabel *latitudeLabel;//显示地区的纬度
@property (strong, nonatomic) IBOutlet UILabel *detailAddressLabel;//显示地区的详细信息
#pragma mark - 反地理编码 根据经纬度获取对应的地区名
@property (strong, nonatomic) IBOutlet UITextField *reverseLongtitudeField;//查询地区的经度
@property (strong, nonatomic) IBOutlet UITextField *reverseLatitudeField;//查询地区的纬度
@property (strong, nonatomic) IBOutlet UILabel *reverseDetailAddressLabel;//查询的地区详细名称
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化地理编码器
if (!_geocoder) {
_geocoder = [[CLGeocoder alloc] init];
}
}
// 地理编码
- (IBAction)geocoder:(id)sender {
//获取输入的城市名称
NSString *address = self.addressField.text;
if (address.length == 0) return;
[self getCoordinateByAddress:address withBlock:^(CLPlacemark *pm) {
// 设置经纬度
self.latitudeLabel.text = [NSString stringWithFormat:@"%.2f", pm.location.coordinate.latitude];
self.longitudeLabel.text = [NSString stringWithFormat:@"%.2f", pm.location.coordinate.longitude];
// 设置具体地址
self.detailAddressLabel.text = pm.name;
} withfaild:^(NSError *error) {
self.detailAddressLabel.text = @"你找的地址可能不存在,请重新输入";
self.addressField.text = @"";
}];
}
- (void)getCoordinateByAddress:(NSString *)address withBlock:(void(^)(CLPlacemark *pm))block withfaild:(void(^)( NSError *error))faild{
[_geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) { // 有错误(地址乱输入)
faild(error);
} else { // 编码成功
// 取出最前面的地址(数组中可能存在过河产讯到的地址)
CLPlacemark *pm = [placemarks firstObject];
// 回调数据
block (pm);
NSLog(@"总共找到%ld个地址", placemarks.count);
//遍历数组,获取所有查找到的城市
for (CLPlacemark *pm in placemarks) {
NSLog(@"-----地址开始----");
// 枚举编译出得所有的地理信息
[pm.addressDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"%@:%@", key, obj);
}];
NSLog(@"-----地址结束----");
}
}
}];
}
- (IBAction)reverseGeocoder:(id)sender {
// 1.包装位置,将输入的字符串设置为地理坐标
CLLocationDegrees latitude = [self.reverseLatitudeField.text doubleValue];
CLLocationDegrees longitude = [self.reverseLongtitudeField.text doubleValue];
CLLocation *loc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
[self getAddressByLocation:loc withBlock:^(CLPlacemark *pm) {
// 设置具体地址
self.reverseDetailAddressLabel.text = [NSString stringWithFormat:@"%@ %@ %@ %@ %@",pm.country,pm.locality,pm.subLocality,pm.thoroughfare,pm.subThoroughfare];
} withFaild:^(NSError *error) {
self.reverseDetailAddressLabel.text = @"你找的地址可能只在火星有!!!";
}];
}
- (void)getAddressByLocation:(CLLocation *)loc withBlock:(void(^)(CLPlacemark *pm))block withFaild:(void(^)(NSError *error))faild {
// 2.反地理编码
[_geocoder reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) { // 有错误(地址乱输入)
faild (error);
} else { // 编码成功
// 取出最前面的地址
CLPlacemark *pm = [placemarks firstObject];
block (pm);
NSLog(@"总共找到%ld个地址", placemarks.count);
for (CLPlacemark *pm in placemarks) {
NSLog(@"-----地址开始----");
// 获取所有的地标信息
[pm.addressDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"%@:%@", key, obj);
}];
NSLog(@"-----地址结束----");
}
}
}];
}
@end
标签:
原文地址:http://blog.csdn.net/qq_32510689/article/details/51721265