1.Core Location 1.获取权限 要开始定位服务的第一步就是获取用户权限,在iOS8之前,获取位置服务权限是隐式的。你只要初始化CLLocationManager并设置它的delegate
1.Core Location
1.获取权限
要开始定位服务的第一步就是获取用户权限,在iOS8之前,获取位置服务权限是隐式的。你只要初始化CLLocationManager并设置它的delegate为它的owner,当你开始调用startUpdatingLocation进行定位时,如果应用程序还没有被许可或者之前被拒绝了的话,会触发系统弹出提示框向用户获取位置服务的授权:
@interface ViewController ()<CLLocationManagerDelegate>{ CLLocationManager *locationmanager;}- (void)viewDidLoad { [super viewDidLoad]; self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; [self.locationManager startUpdatingLocation];}
即让 CLLocationManager 取得最新的位置的这个操作会让系统弹出是否允许位置服务的提示。
在 iOS 8,取得权限和使用位置服务分成两个动作了。分别用两个不同的方法取得权限:requestWhenInUseAuthorization 和 requestAlwaysAuthorization。前者只能让应用在使用的时候有权获取位置数据;后者会得到跟上面讲的一样获得后台位置服务。
- (void)viewDidLoad { [super viewDidLoad]; if (IS_IOS8) { locationmanager = [[CLLocationManager alloc] init]; [locationmanager requestAlwaysAuthorization]; [locationmanager requestWhenInUseAuthorization]; locationmanager.delegate = self; } }
注意你需要判断设备是不是iOS8才可以使用上面的方法,否则locationmanager不响应相应的方法会造成应用奔溃:
#define IS_IOS8 ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8)
或者也可以这样:
if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { [self.locationManager requestWhenInUseAuthorization]; }
在iOS8之前,你可以选择性在 Info.plist 中包含 'NSLocationUsageDescription' 的关键字。这个值是一个纯文本的字符串,向用户说明了应用要使用位置服务的目的。现在这个值被拆分成了两个不同的关键字:NSLocationWhenInUseUsageDescription和 NSLocationAlwaysUsageDescription,而且是必填的;如果你不添加对应的关键字就去调用 requestWhenInUseAuthorization或requestAlwaysAuthorization,那么将不会有任何的弹出提示给用户。
那么下面这样设置是对的吗?
注意不能像上图那样设置为两个BOOL值,那样会导致用户在隐私设置页面打开你的应用的定位服务设置后使得设置页面奔溃。因为上面两个关键字对于的必须是字符串,不能是bool值。
下面这样才是正确的(我以前就在这里被其它文章误导所以使用bool值)
注意授权弹出框会只显示一次。在CLLocationManager.authorizationStatus()返回除NotDetermined之外的值之后,不管调用requestWhenInUseAuthorization()或requestAlwaysAuthorization()都不会有一个 UIAlertController 显示出来了。在用户最初的选择之后,唯一改变授权的方式是到隐私设置中设置。
为了快速到达隐私设置页面,Apple引入UIApplicationOpenSettingsURLString,它存储了一个 URL 用来打开当前应用在 Settings.app 对应的页面。如下:
- (IBAction)setting:(id)sender { if (&UIApplicationOpenSettingsURLString != NULL) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; }}
2.CLLocationManager
如上讲的,CLLocationManager是管理我们的定位服务的,CLLocationManager包括下列属性:
desiredAccuracy是指定位精度,它是个枚举类型:
kCLLocationAccuracyBest:最精确定位
CLLocationAccuracy kCLLocationAccuracyNearestTenMeters:十米误差范围
kCLLocationAccuracyHundredMeters:百米误差范围
kCLLocationAccuracyKilometer:千米误差范围
kCLLocationAccuracyThreeKilometers:三千米误差范围
distanceFilter是位置信息更新最小距离,只有移动大于这个距离才更新位置信息,默认为kCLDistanceFilterNone:不进行距离限制。
stopUpdatingLocation:停止定位追踪
startUpdatingHeading:开始导航方向追踪
stopUpdatingHeading:停止导航方向追踪
startMonitoringForRegion::开始对某个区域进行定位追踪,开始对某个区域进行定位后。如果用户进入或者走出某个区域会调用
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
和
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
stopMonitoringForRegion:停止对某个区域进行定位追踪
requestWhenInUseAuthorization:请求获得应用使用时的定位服务授权
requestAlwaysAuthorization:请求获得应用一直使用定位服务授权,
我们从一个例子开始讲起,先完成一些需要的工作:
记住,你需要把MapKit framework加入到项目中. (Control + Click Frameworks folder -> Add -> Existing Frameworks)
为了分离控制器,我们新建一个NSObject子类,
MyLocation.h
#import <MapKit/MapKit.h>#import <CoreLocation/CoreLocation.h>@interface MyLocation : NSObject<CLLocationManagerDelegate,UIAlertViewDelegate>@end
MyLocation.m
-(void)startLocation{ if([CLLocationManager locationServicesEnabled] && [CLLocationManager authorizationStatus] != kCLAuthorizationStatusDenied) { _manager=[[CLLocationManager alloc]init]; _manager.delegate=self; _manager.desiredAccuracy = kCLLocationAccuracyBest; _manager.distanceFilter=100; [_manager requestAlwaysAuthorization]; [_manager startUpdatingLocation]; } else { UIAlertView *alvertView=[[UIAlertView alloc]initWithTitle:@"提示" message:@"你还未开启定位服务" delegate:nil cancelButtonTitle:@"设置" otherButtonTitles: @"取消", nil]; alvertView.delegate = self; [alvertView show]; } }-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if (&UIApplicationOpenSettingsURLString != NULL) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; }}- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{ [self stopLocation];}-(void)stopLocation{ _manager = nil;}
需要注意几点:
1).定位频率和定位精度并不是越精确越好,需要视实际情况而定,因为越精确越耗性能,也就越费电。
2).定位成功后会根据设置情况频繁调用
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
方法,这个方法返回一组地理位置对象数组,每个元素一个CLLocation代表地理位置信息(包含经度、纬度、海报、行走速度等信息),之所以返回数组是因为有些时候一个位置点可能包含多个位置。
3).使用完定位服务后如果不需要实时监控应该立即关闭定位服务以节省资源。
4).除了提供定位功能,CLLocationManager还可以调用startMonitoringForRegion:方法对指定区域进行监控。
3.实例
对于CLLocation对象,适合使用单例来创建它
+ (MyLocation *)shareLocation{ static dispatch_once_t pred = 0; __strong static id _sharedObject = nil; dispatch_once(&pred, ^{ _sharedObject = [[self alloc] init]; }); return _sharedObject;}
以下协议会在startUpdatingLocation后被调用,我们可以在这个方法中得到我们需要的信息:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ CLLocation *loc = [locations firstObject]; self.lastCoordinate = loc.coordinate;}
我们通过Block传递消息到控制类:
typedef void (^locationCorrrdinate)(CLLocationCoordinate2D locationCoordinate);...省略-(void)getLocationMessageCoordinate2D:(locationCorrrdinate)locationCoordinate{ [self startLocation]; locationCoordinate(self.lastCoordinate);}
ViewController.m
- (IBAction)getLocationMessage:(UIButton *)sender { __block __weak ViewController *wself = self; [[MyLocation shareLocation]getLocationMessageCoordinate2D:^(CLLocationCoordinate2D locationCoordinate) { NSLog(@"纬度--%f 经度----%f",locationCoordinate.latitude,locationCoordinate.longitude); NSString *str = [NSString stringWithFormat:@"%f,%f",locationCoordinate.latitude,locationCoordinate.longitude]; wself.coordinateLabel.text = str; }];}
现在我们运行应用,现在你按下location,你可以看到:
正如我们所期望的,如果你点击不允许之后,继续点击location按钮,你会看到:
点击设置选项可以快速跳转到地理位置服务设置页面。
不过有一点问题,在我们允许使用地理位置服务后,最初我们按下按钮的时候,显示为0,再按下才有:
2015-03-16 22:19:06.528 LocationDemo[2420:1034611] 纬度--0.000000 经度----0.0000002015-03-16 22:19:09.445 LocationDemo[2420:1034611] 纬度--23.046266 经度----113.386919
因为设备获取经纬度需要一定的时间,最初按下的时候还没有获取到经纬度值。下面的代码是解决办法。
@property (nonatomic,strong)locationCorrrdinate locationCoordinateBlock;...省略-(void)getLocationMessageCoordinate2D:(locationCorrrdinate)locationCoordinate{ self.locationCoordinateBlock = [locationCoordinate copy]; [self startLocation];}-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ CLLocation *loc = [locations firstObject]; self.lastCoordinate = loc.coordinate; self.locationCoordinateBlock(loc.coordinate);}
注意,我们之所以需要[locationCoordinate copy]是因为locationCoordinate是一个block,分配在栈上,超过它的作用域之后block就不在了。所以必须先copy到heap上,这样就可以在之后的使用中正常访问。我在这篇文章中曾详细讲过。
现在运行一切正常。
3.地理位置编码
为了将你上面得到的纬度和经度显示为真实的地址,你需要使用地理位置编码。
使用CLGeocoder可以完成“地理编码”和“反地理编码”
地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
反地理编码:根据给定的经纬度,获得具体的位置信息
地理编码方法
- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
反地理编码方法
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;
我们先看反地理编码方法,代码如下:
-(void)getLocationMessageCoordinate2D:(locationCorrrdinate)locationCoordinate address:(address)address detailAddress:(address)detailAddress{ self.locationCoordinateBlock = [locationCoordinate copy]; self.addressBlock = [address copy]; self.detailAddressBlock = [detailAddress copy]; [self startLocation];}-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ CLLocation *loc = [locations firstObject]; self.coordinate = loc.coordinate; self.locationCoordinateBlock(loc.coordinate); CLGeocoder *geocoder = [[CLGeocoder alloc]init]; [geocoder reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) { if (placemarks.count > 0) { CLPlacemark *placemark = [placemarks objectAtIndex:0]; _address = [NSString stringWithFormat:@"%@%@",placemark.administrativeArea,placemark.locality]; _detailAddress = [NSString stringWithFormat:@"%@",placemark.name];//详细地址 // 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; //关联的或利益相关的地标 } if (self.addressBlock) { self.addressBlock(_address); } if (self.detailAddressBlock) { self.detailAddressBlock(_detailAddress); } }];}
ViewController.m
- (IBAction)getAddress:(UIButton *)sender { __block __weak ViewController *wself = self; [[MyLocation shareLocation]getLocationMessageCoordinate2D:^(CLLocationCoordinate2D locationCoordinate) { } address:^(NSString *string) { wself.addressLabel.text = string; } detailAddress:^(NSString *string) { }];}- (IBAction)getDetailAddress:(UIButton *)sender { __block __weak ViewController *wself = self; [[MyLocation shareLocation]getLocationMessageCoordinate2D:^(CLLocationCoordinate2D locationCoordinate) { // } address:^(NSString *string) { // } detailAddress:^(NSString *string) { wself.detailAddressLabel.text = string; }];}
好了,运行效果如下:
看一些地理编码:根据给定的地名,获得具体的位置信息,如下:
-(void)geocodeAddress{ CLGeocoder *geocoder = [[CLGeocoder alloc]init]; [geocoder geocodeAddressString:@"广东工业大学" completionHandler:^(NSArray *placemarks, NSError *error) { if (placemarks.count>0) { CLPlacemark *placemark=[placemarks firstObject]; CLLocation *location=placemark.location;//位置 CLRegion *region=placemark.region;//区域 NSLog(@"位置:%@,区域:%@",location,region); } }];}
打印结果如下;
2015-03-17 13:19:02.179 LocationDemo[2755:1160527] 位置:<+23.13312700,+113.29970500> +/- 100.00m (speed -1.00 mps / course -1.00) @ 15/3/17 中国标准时间下午1:19:02,区域:CLCircularRegion (identifier:'<+23.13312650,-66.70029500> radius 14867919.48', center:<+23.13312650,-66.70029500>, radius:14867919.48m)
文/庄洁元(简书作者)
原文链接:http://www.jianshu.com/p/5ce59ffff2e0
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。