地图的定位与导航
2017-06-01 本文已影响20人
block_smile
1、定位
获取定位需要用到 CoreLocation/CoreLocation.h 框架
1)一次定位
步骤:
1、创建位置管理器
// 这里创建的管理其对象如果没有强引用,就会造成你后面的操作不会出现效果,全局变量强引用.
CLLocationManager *manager = [[CLLocationManager alloc] init];
self.manager = manager;
2、请求授权
① 设置info.plist -> Privacy - Location When In Use Usage Description(请求授权时的弹窗所显示的内容)
② 请求授权代码: [mgr requestWhenInUseAuthorization]
3、定位(设置代理:CLLocationManagerDelegate)
manager.delegate = self;
4、开始并获取定位
//开始定位
[mgr startUpdatingLocation]
//在代理方法中获取位置信息。该方法在获取到位置后自动调用,无论位置是否发生变化
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{
CLLocation *location = locations.lastObject;
NSLog(@"%f,%f", location.coordinate.latitude, location.coordinate.longitude);
//停止定位
[manager stopUpdatingLocation];
/**
* 无法获取定位的情况:
1> mgr没有强引用
2> 请求授权没有设置info.plist
3> 模拟器没有设置
4> 模拟器bug 切换模拟器
*/
}
2) 持续定位
持续定位在一次定位的基础上,额外设置两个属性即可:
//优化持续定位
//设置距离筛选器 减少不必要的监听回调
manager.distanceFilter = 10;
//设置期望精确度 减少不必要的能耗(精确度要求的越低,手机能耗越小)
manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
3) 定位时请求用户授权
请求授权有三种方式,每种都需要配置info.plist
第一种,请求应用在使用时(应用在前台运行)可以获取定位信息
//设置info.plist -> Location When In Use Usage Description
[manager requestWhenInUseAuthorization];
第二种, 请求应用始终(应用在前台&后台运行时)可以获取定位信息 缺点: 应用在后台执行一段时间后就会挂起
//设置info.plist -> NSLocationAlwaysUsageDescription
[mgr requestAlwaysAuthorization];
第三种,后台运行模式(应用在任何模式下)获取定位信息 设置info.plist, 还需要代码设置
//1) **配置info.plist相关key** ( 这里需要新加一个选项 选择: Required background modes->打开items->选取App registers for location updates(定位后台运行的模式)) — 在ios8 之前只要设置这个就行了
//2) 在ios8以后就是**ios9的一个新特性需要再加上一句代码 才能执行这个功能**,即
mgr.allowsBackgroundLocationUpdates = YES;
第三种除了做以上工作外,还需将第一种前台请求授权打开,也就是需要将第一种授权也同时完整做一遍
三种方式对比:
第一种对app有较大限制,应用进入后台就无法获取定位(不建议)
第二种用户较为反感,可能会被用户禁止定位(不建议)
第三种是应用程序临时进入后台时也可以请求授权。是较为常用的方式
2、地理编码与反地理编码
1)地理编码
1.创建地理编码者
CLGeocoder *geocoder = [CLGeocoder new];
2.进行地理编码
人文-> 地理
[geocoder geocodeAddressString:@"地理位置字符串" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (placemarks.count == 0 || error) {
NSLog(@"%@", error);
return ;
}
//CLPlacemark 地标对象 包含地理信息&人文信息
CLPlacemark *pm = placemarks.lastObject;
//设置数据
self.latitudeLabel.text = [NSString stringWithFormat:@"%f", pm.location.coordinate.latitude];
self.longitudeLabel.text = [NSString stringWithFormat:@"%f", pm.location.coordinate.longitude];
self.detailLabel.text = pm.name;
}];
2) 反地理编码
地理 -> 人文
1.创建地理编码者
CLGeocoder *geocoder = [CLGeocoder new];
//封装位置对象,根据经纬度创建位置对象
CLLocation *location = [[CLLocation alloc]initWithLatitude:[self.latitudeTF.text floatValue] longitude:[self.longitudeTF.text floatValue]];
2.进行反地理编码
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (placemarks.count ==0 || error) {
return ;
}
CLPlacemark *pm = placemarks.lastObject;
//设置数据
self.cityLabel.text = pm.locality;
}];
3、对系统地图的基本使用
1、系统地图基本使用流程:
1,创建MapView ->
- ps:地图相关需要导入MapKit类库,而系统也会根据代码自动导入某些类库(包括MapKit),所以如果运行代码时因为缺少类库报错,则需手动导入,如果没有,那就是系统已经帮你导入了
2,请求授权 ->
- 相关定位授权前面有讲
3,设置地图属性,包括:
//显示定位点
self.mapView.showsUserLocation = YES;
//设置用户跟踪模式
self.mapView.userTrackingMode = MKUserTrackingModeFollow;
//显示标尺
self.mapView.showsScale = YES;
//显示指南针
self.mapView.showsCompass = YES;
//显示交通
self.mapView.showsTraffic = YES;
//地图更新到位置后再来进行反地理编码 设置代理
self.mapView.delegate = self;
//设置地图类型,标准地图,卫星地图,混合地图等
self.mapView.mapType = MKMapTypeStandard;
4,设置地图位置返回到定位点
第一种方式
带动画的设置用户跟踪模式
// self.mapView.userTrackingMode = MKUserTrackingModeFollow;
[self.mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
第二种方式
//设置地图范围 中心点变为定位点,跨度使用当前地图的跨度
[self.mapView setRegion:MKCoordinateRegionMake(self.mapView.userLocation.location.coordinate, self.mapView.region.span) animated:YES];
/**
MKCoordinateRegion是一个结构体
* 地图范围
typedef struct {
CLLocationCoordinate2D center; 中心点 用于确定地图位置
MKCoordinateSpan span; 经纬度跨度 用于确定地图宽高尺寸
} MKCoordinateRegion;
*/
5,地图放大和缩小
- 地图放大和缩小的原理与上面返回定位点中的第二种方式一样。不同之处在于地图放大缩小时,中心位置不变,地图跨度改变
//地图的中心点保持不变,地图跨度根据比例系数进行改变
MKCoordinateSpan span = MKCoordinateSpanMake(self.mapView.region.span.latitudeDelta * 0.5, self.mapView.region.span.longitudeDelta * 0.5);
MKCoordinateRegion region = MKCoordinateRegionMake(self.mapView.region.center, span);
[self.mapView setRegion:region animated:YES];
6,添加大头针视图
- 大头针视图不是靠程序员使用addSubView等方式添加的,而是有系统自动添加。但程序员可以设置大头针的相关数据(经纬度,标注信息)
添加大头针时,我们需要提供大头针模型。
系统需要通过我们提供的大头针模型来获取我们想设置的大头针的相关数据。大头针模型可以是任意类,只要它遵守了MKAnnotation协议.这里使用了装饰器模式,稍后再详细讲解装饰器模式。
MKAnnotation协议中声明了两个属性(title和subtitle),大头针视图类会因为遵守了该协议而多了这两个属性的声明,和这两个属性的set&get方法的声明。我们需要在大头针视图类的.m中实现这两个属性的set&get方法。
同时,在MKAnnotation协议中"提示"我们还有一个coordinate属性需要我们自己声明,大头针视图类在声明了该属性后便可以通过给该属性赋值,来确定将大头针添加到哪个经纬度坐标
关于装饰器模式
装饰器模式也被称为装饰者模式
装饰器模式是为了给某个类扩展功能,若一个类遵守了某个装饰器协议,就可以拥有该协议赋予的功能。
例如需要归档解档时,需要遵守NSCoding协议,那这个类就可以具有归档解档的功能,以及相关的方法。
同样,如果我们需要让一个类可以为大头针视图设置数据,就需要遵守MKAnnotation,并实现相关的方法即可。
装饰器可以间接的实现“多继承”,即不需要继承自某个类就可以拥有该类的某些功能。
因此装饰器也具有解耦和的作用,因为它不需要继承指定类型
装饰器与代理的区别
1> 代理主要用于解耦和的方式传递数据 两个类耦合度很低
2> 装饰器主要用于拓展功能
装饰器模式除了使用正式协议(protocol)还可以使用非正式协议(NSObject的分类就称为非正式协议), 例如KVO。KVO的方法是写在NSObject的分类中的。
这种情况下,对象不需要遵守某个协议就可以具有此方法。因为所有类都继承自NSObject,所以NSObject分类中的方法,所有的对象都会具有。
这也是变相的实现了装饰器模式
总之,使用装饰器,只需遵守协议,实现方法,就可以了。
示例代码:
//添加大头针模型 可以是任意类型,但是必须要遵守MKAnnotation协议
HMAnnotation *anno = [HMAnnotation new];
//根据点击位置设置经纬度
//可以根据touch获取CGPoint
UITouch *touch = touches.anyObject;
CGPoint point = [touch locationInView:self.mapView];
//转化坐标系(iOS坐标系&经纬度进行转换)
anno.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
//anno.coordinate = CLLocationCoordinate2DMake(39.9, 116.4);
[self.mapView addAnnotation:anno];
7,自定义大头针视图
- 自定义大头针视图与自定义tableViewCell极为相似,都使用了重用机制,连方法名都很类似
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
//过滤定位大头针,防止将“我的位置”的大头针视图换掉
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
static NSString *identifier = @"anno";
//取
MKAnnotationView *annoV = [self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
//没有,创建
if (annoV == nil) {
annoV = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
//设置图片
annoV.image = [UIImage imageNamed:@"苍老师"];
//显示标注
annoV.canShowCallout = YES;
}
//使用
return annoV;
}
2、使用系统地图导航
使用系统地图导航的核心方法是:
[MKMapItem openMapsWithItems:@[source, dest] launchOptions:@{MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeWalking, MKLaunchOptionsMapTypeKey: @(MKMapTypeSatellite), MKLaunchOptionsShowsTrafficKey: @YES}];
上面方法中我们需要提供起点和终点,而方法中要求起点和终点是MKMapItem *类,并放到数组中。我们可以通过多种方式创建想要的MKMapItem *类,比如:
1.//通过当前定位点来创建
[MKMapItem mapItemForCurrentLocation];
2.//通过地标对象生成MKPlacemark。
//这个通常用于地理编码方法中,在地理编码方法中可以得到placemarks
MKPlacemark *pm = [[MKPlacemark alloc] initWithPlacemark:placemarks.lastObject];
//创建地图项目 主要用于在系统地图上设置某个点的信息
MKMapItem *dest = [[MKMapItem alloc] initWithPlacemark:pm];
3、自定义导航
- 自定义导航同样需要设置起点、终点、交通方式等。流程略有不同
① 创建导航请求
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
② 上面的导航请求中包含,起点、终点、交通方式等属性。只需依次进行设置即可
request.source = [MKMapItem mapItemForCurrentLocation];
...
③ 使用导航请求,创建导航对象。并且发起请求。另外还需将路线通过addOverlay绘制出来
//创建导航对象
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
//计算路线(发起请求)
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
//取出一条路线
MKRoute *route = response.routes.lastObject;
//MKPolyline 地图折线类型 折线属于地图覆盖物(任意类型,遵守MKOverlay)的一种
//添加地图覆盖物 还需要设置覆盖物的样式
[self.mapview addOverlay:route.polyline];
//4.取出每一步的内容
//for (MKRouteStep *step in route.steps) {
//NSLog(@"%@", step.instructions);
//}
}];
4、集成第三方地图
- 第三方地图SDK的集成可使用Cocoapods,也可手动导入,若手动导入,记得导入相关的依赖库,可根据第三方平台的操作提示,集成第三方主要看文档,遇到问题解决问题,此处不再详细介绍.谢谢.