前言:
苹果在天朝使用的是高德地图,使用起来还算是比较方便,当然在国内还有百度地图sdk也很不错.我这里简单介绍下苹果原生的地图使用方式,很多地方也许说的不到位,还请看出来的大神指点一下,文中有些地方我觉得用我自己的话解释可能说的不是很清楚,所以有些地方是从网上摘抄,请不要介意.
在使用苹果地图时需要导入MapKit 框架,但是与这个框架生死相随的另外一个框架也是很重要的,那就是CoreLocation 框架,我这里就首先从这个CoreLocation框架先简单的介绍下.
一.CoreLocation定位服务
顾名思义,这个英文单词汉语的直译就是核心定位,要想使用这个库,需要以下三个步骤:
1.在TARGETS->Build Phases->Link Binary With Libraries中导入 CoreLocation.framework 静态库
2.在info.plist中加入对应的缺省字段 ,值设置为YES(前台定位写上边字段,前后台定位写下边字段)
<key>NSLocationWhenInUseUsageDescription</key> //允许在前台获取GPS的描述
<true/>
<key>NSLocationAlwaysUsageDescription</key> //允许在前、后台获取GPS的描述
<true/>
3.在需要使用定位服务的类中导入#import <CoreLocation/CoreLocation.h>
我现在的解决办法:
1)在Capabilities中选择BackgroundModes,并且勾选Location updates。
2)在info.plist中添加Privacy - Location Usage Description字段,后面添加你想描述的文字。
3)在创建manager的方法中实现以下代码。
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8){
//[_locationManager requestWhenInUseAuthorization];//只在前台开启定位
[self.manager requestAlwaysAuthorization];//在后台也可定位
}
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9){
self.manager.allowsBackgroundLocationUpdates = YES;
}
做完以上步骤就可以使用定位服务了.
从iOS 6开始,苹果在保护用户隐私方面做了很大的加强,以下操作都必须经过用户批准授权
要想获得用户的位置;
想访问用户的通讯录、日历、相机、相册等等;
当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权.
在CoreLocation中左右数据类型的前缀都是CL开头的,并且使用CoreLocationManager对象来做用户定位的相关操作.
CLLocationManager *manager = [[CLLocationManager alloc]init];
// 开始定位用户的位置
[manager startUpdatingLocation];
// 停止定位用户的位置
[manager stopUpdatingLocation];
遵循<CLLocationManagerDelegate>设置好代理,并且调用上述的startUpdatingLocation方法后
manager.delegate = self;
这时系统会频繁的调用下述代理方法
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations;
其中locations数组中装载着的是CLLocation对象,也就是存储着一系列用户位置的数组,其中这个数组的第一个对象是比较准确的位置.(ps.手机个用户定位时并不是一个点的位置,而是一系列的定位的点)
CLLocation是用来表示用户位置的地理信息,一下介绍介个常用的属性
//经纬度, CLLocationCoordinate2D是个结构体;
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate;
//海拔, CLLocationDistance就是一个double类型;
@property(readonly, nonatomic) CLLocationDistance altitude;
//路线,航向, CLLocationDirection也是一个double类型的值,范围在0~359.9之间,0表示正北方向,负值无效;
@property(readonly, nonatomic) CLLocationDirection course;
//速度, CLLocationSpeed也是一个double类型数据,单位是m/s;
@property(readonly, nonatomic) CLLocationSpeed speed;
//计算两个位置之间的距离;
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location;
//计算当前位置和所给位置的距离;
- (CLLocationDistance)getDistanceFrom:(const CLLocation *)location;
CLLocationCoordinate2D是一个用来表示经纬度的结构体,定义如下
typedef struct {
CLLocationDegrees latitude; // 纬度
CLLocationDegrees longitude; // 经度
} CLLocationCoordinate2D;
一般用CLLocationCoordinate2DMake函数来创建CLLocationCoordinate2D
CLLocationManager用来作为定位服务的管理者,苹果官方给出的解释是一个定位服务的接入点,几乎定位服务的大部分功能由它来调度
//判断定位服务能否开启;
+ (BOOL)locationServicesEnabled;
//指定每个多少米更新一次距离;
@property(assign, nonatomic) CLLocationDistance distanceFilter;
//指定定位精度,但是会更加的消耗你的电能,请在正确的场景使用;
@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;
定位精度在以下的定义中
extern const CLLocationAccuracy kCLLocationAccuracyBestForNavigation ;
extern const CLLocationAccuracy kCLLocationAccuracyBest;
extern const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;
extern const CLLocationAccuracy kCLLocationAccuracyHundredMeters;
extern const CLLocationAccuracy kCLLocationAccuracyKilometer;
extern const CLLocationAccuracy kCLLocationAccuracyThreeKilometers;
二.CLGeocoder 地理编码 和 反地理编码
地理编码 : 根据用户给定的地名,来获得具体的地理位置信息
反地理编码 : 根据用户给定的经纬度,来获得具体的位置信息(常用的场景就是用户定位获取到了自己的经纬度,然后通过反编码来判断所处的位置,因为用户仅仅只知道自己的经纬度没用)
//地理编码方法
//根据包含地址信息的指点,来编码地理信息
- (void)geocodeAddressDictionary:(NSDictionary *)addressDictionary completionHandler:(CLGeocodeCompletionHandler)completionHandler;
//根据置顶的位置信息来编码地理信息
- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
//根据指定的位置信息和范围来编码地理信息
- (void)geocodeAddressString:(NSString *)addressString inRegion:(nullable CLRegion *)region completionHandler:(CLGeocodeCompletionHandler)completionHandler;
其中CLGeocodeCompletionHandler在系统中的定义是一个代码块,定义如下
typedef void (^CLGeocodeCompletionHandler)(NSArray< CLPlacemark *> * __nullable placemarks, NSError * __nullable error);
地理编码方法编码成功时,会进入到这个代码块中执行
placemarks:里面装载着的是CLPlacemark对象;
error:当编码出错时,存储异常信息;
CLPlacemark中封装着详细的地址位置信息
//地理位置
@property (nonatomic, readonly, copy, nullable) CLLocation *location;
//区域
@property (nonatomic, readonly, copy, nullable) CLRegion *region;
//详细的地址信息
@property (nonatomic, readonly, copy, nullable) NSDictionary *addressDictionary;
//地址名称
@property (nonatomic, readonly, copy, nullable) NSString *name;
//城市
@property (nonatomic, readonly, copy, nullable) NSString *locality;
还有更多的属性,在文档中都有说明.
//反地理编码的方法,用法和上面差不多
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;
其实在这些定位,编码和反编码的类中,也许会有很多让人模糊的属性对象,但是我们只要按住command+鼠标点按,就能看到苹果官方是怎么样定义这些属性的,然后根据对应的中文意思,也就能大概的推测出相应的功能.
三.地图
写完定位终于到重点了,MapKit框架.这是苹果原生自带的地图框架,在我大天朝还有一个响亮的名字,没错,就是高德地图.和CoreLocation一样也有下面两个步骤:
1.在TARGETS->Build Phases->Link Binary With Libraries中导入 MapKit.framework 静态库.
2.在需要使用定位服务的类中导入#import <MapKit/MapKit.h>.
3.创建MKMapView控件,这样就可以在界面上显示地图了.
MKMapView控件中有个userTrackingMode属性
@property (nonatomic) MKUserTrackingMode userTrackingMode NS_AVAILABLE(NA, 5_0);
这个是用来跟踪显示用户当前位置的,它有三种属性:
定义如下:
typedef NS_ENUM(NSInteger, MKUserTrackingMode) {
MKUserTrackingModeNone = 0, // the user's location is not followed
MKUserTrackingModeFollow, // the map follows the user's location
MKUserTrackingModeFollowWithHeading, // the map follows the user's location and heading
} NS_ENUM_AVAILABLE(NA, 5_0) __WATCHOS_PROHIBITED;
MKUserTrackingModeNone : 不跟踪用户位置
MKUserTrackingModeFollow : 跟踪并且在地图上显示用户位置
MKUserTrackingModeFollowWithHeading : 跟踪并且在地图上显示用户位置,并且还会跟随用户前进方向旋转
MKMapView控件中有个mapType属性
@property (nonatomic) MKMapType mapType;
这个是用来设置地图类型的,在iOS9之前,它有三种类型:(普通地图,卫星云图,普通地图和卫星云图的混合)
但在iOS9之后出现了两个新的类型,SatelliteFlyover(地形和建筑物的三维模型)和HybridFlyover()
定义如下:
typedef NS_ENUM(NSUInteger, MKMapType) {
MKMapTypeStandard = 0,
MKMapTypeSatellite,
MKMapTypeHybrid,
MKMapTypeSatelliteFlyover NS_ENUM_AVAILABLE(10_11, 9_0),
MKMapTypeHybridFlyover NS_ENUM_AVAILABLE(10_11, 9_0),
} NS_ENUM_AVAILABLE(10_9, 3_0) __WATCHOS_PROHIBITED;
我这里就不贴出图片了.
通过MKMapView的下列方法,可以设置地图显示的位置和区域
设置地图的中心点位置
@property(nonatomic)CLLocationCoordinate2D centerCoordinate;
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated;
设置地图的显示区域
@property (nonatomic) MKCoordinateRegion region;
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated;
上面的CLGeocoder中提到的CLPlacemark也有个CLRegion属性,他们都是区域范围的意思,
这里的定义是一个结构体,里面包含了两个数据类型,一个是中心点,一个是区域的跨度,简单的理解就是,你有一个中心点后,系统要知道你要现实的具体范围吧,总不能知道了你要显示的中心点,但是系统把整个中国都显示出来吧,就类似于你主动把精确度给系统来进行显示.
typedef struct {
CLLocationCoordinate2D center; //区域中心点
MKCoordinateSpan span; //区域跨度
} MKCoordinateRegion;
typedef struct {
CLLocationDegrees latitudeDelta; //纬度跨度
CLLocationDegrees longitudeDelta; //精度跨度
} MKCoordinateSpan;
MKMapView中还有很多常用的属性,我感觉直接按住command+鼠标点进去看看就差不多了.用翻译软件翻译一下中文意思就差不多了.
同其他的UI控件一样,MKMapView也有自己的代理方法,方法很多,我这里也没办法一一例举出来,常见的代理方法有:
//调用非常频繁,不断监测用户的当前位置
//每次调用,都会把用户的最新位置(userLocation参数)传进来
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation;
//地图的显示区域即将发生改变的时候调用
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
//地图的显示区域已经发生改变的时候调用
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
//添加大头针的时候调用,返回annotationView
- (
MKAnnotationView
*)mapView:(
MKMapView
*)mapView viewForAnnotation:(id<
MKAnnotation
>)annotation;
//点击大头针的时候调用
- (
void
)mapView:(
MKMapView
*)mapView didSelectAnnotationView:(
MKAnnotationView
*)view;
MKUserLocation 其实就是一个大头针模型,就是上图显示的那个蓝色的标志.
#import <MapKit/MKFoundation.h>
#import <MapKit/MKAnnotation.h>
@class CLLocation;
@class MKUserLocationInternal;
@interface MKUserLocation : NSObject <MKAnnotation>
@property (readonly, nonatomic, getter=isUpdating) BOOL updating;
@property (readonly, nonatomic, nullable) CLLocation *location;// 地理位置信息
@property (readonly, nonatomic, nullable) CLHeading *heading;// 这是iOS9新增的属性,一个指北针所包含的信息
@property (nonatomic, copy, nullable) NSString *title;// 显示在大头针上的标题
@property (nonatomic, copy, nullable) NSString *subtitle;// 显示在大头针上的子标题
@end
大头针的基本操作
//添加一个大头针
- (void)addAnnotation:(id <MKAnnotation>)annotation;
//添加多个大头针
- (void)addAnnotations:(NSArray *)annotations;
//移除一个大头针
- (void)removeAnnotation:(id <MKAnnotation>)annotation;
//移除多个大头针
- (void)removeAnnotations:(NSArray *)annotations;
示例:这是一个点击地图的相应位置就添加一个大头针的DEMO,监听点击的方法中,要将获取到得用户点击的point转换为地图上的坐标,HTAnnotation是一个大头针模型,下面回详细介绍.
// 监听mapView的点击事件
[self.mapView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapMapView:)]];
/**
* 监听mapView的点击
*/
- (void)tapMapView:(UITapGestureRecognizer *)tap
{
// 1.获得用户在mapView点击的位置(x,y)
CGPoint point = [tap locationInView:tap.view];
// 2.将数学坐标 转为 地理经纬度坐标
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
// 3.创建大头针模型,添加大头针到地图上
HTAnnotation *anno = [[
HT
Annotation alloc] init];
anno.coordinate = coordinate;
anno.title = @"King";
anno.subtitle = @"King丶逍遥";
[self.mapView addAnnotation:anno];
}
关于大头针模型,其实就是继承于NSObject的类对象,并且要遵循<MKAnnotation>协议.
那么怎么建立大头针模型呢?
请看(这个类的.m文件不需要写东西了)
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface HTAnnotation : NSObject <MKAnnotation>
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@end
也许第一次学习地图的朋友还是不明白为什么会要这么创建的,那么请看官方是怎么定义这个协议的把.
@protocol MKAnnotation <NSObject>
// Center latitude and longitude of the annotation view.
// The implementation of this property must be KVO compliant.
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@optional
// Title and subtitle for use by selection UI.
@property (nonatomic, readonly, copy, nullable) NSString *title;
@property (nonatomic, readonly, copy, nullable) NSString *subtitle;
// Called as a result of dragging an annotation view.
- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate;
@end
看了这些应该知道为什么是那么定义大头针模型了把,要实现<MKAnnotation>协议,就将上述的方法实现,因为大头针包含的是对应的地址信息,所以里面必须包含CLLocationCoordinate2D属性来存储用户地理位置信息,其它的属性也是其它对应的标记,当然你还可以自定义其它的属性,这个在后面也有讲解.下图是title和subtitle对应的现实位置,他们显示的View是MKAnnotationView,设置MKAnnotationView可以显示很多你需要显示的数据.后面会详细说明.
四.自定义大头针
很显然和大多数iOS控件一样,MapView中的大头针也是可以自定义的,但是要如何自定义呢?
首先,设置MKMapView的代理.
其次,实现下面的代理方法,返回大头针控件.
- (nullable MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;
最后,根据传进来的(id <MKAnnotation>)annotation模型参数创建并返回对应的大头针控件.
至于是什么时候调用这个代理方法呢?就是每次在地图上添加大头针时调用,系统自动生成的最前面的蓝色的圆形大头针也会调用这个代理方法.说到这里,也许你对之前的大头针模型理解更深刻了吧,模型中有什么数据,你这里就能使用什么数据来自定义大头针了.不过刚才提到了系统生成的蓝色圆形大头针也会调用这个方法,所以如果不想动这个大头针的话,可以做一次判断.这里提一下,如果返回的是nil,那么返回的是系统的大头针.
MKAnnotationView 大头针控件
@property (nonatomic, strong) id <MKAnnotation> annotation;
大头针模型
@property (nonatomic, strong) UIImage *image;
显示的图片
@property (nonatomic) BOOL canShowCallout;
是否显示标注
@property (nonatomic) CGPoint calloutOffset;
标注的偏移量
@property (strong, nonatomic) UIView *rightCalloutAccessoryView;
标注右边显示什么控件
@property (strong, nonatomic) UIView *leftCalloutAccessoryView;
标注左边显示什么控件
这些是AnnotationView的一些常用属性,至于还有很多其他的属性设置欢迎参考苹果的文档,其实我感觉做了这么久的IOS开发,只有苹果的官方文档才是注释最全面的,而其他的一些学习的书籍反而解释的不是很透彻,这就需要一个程序员有一定的阅读英文的能力,不过话说回来,我的英文水平很菜,大学时,英语四级也没过,不过,这不影响我偶尔阅读API文档,慢慢看,不懂得就用翻译软件,总还是能看懂的.好吧,下面就放一段annotationView的示例用法:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if (![annotation isKindOfClass:[zidigyiAnnotation class]]) return nil;
static NSString *ID = @"zidingyi";
// 从缓存池中取出可以循环利用的大头针view
MKAnnotationView *annoView = [mapView dequeueReusableAnnotationViewWithIdentifier:ID];
if (annoView == nil) {
annoView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID];
// 显示子标题和标题
annoView.canShowCallout = YES;
// 设置大头针描述的偏移量
annoView.calloutOffset = CGPointMake(0, -10);
// 设置大头针描述右边的控件
annoView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeContactAdd];
// 设置大头针描述左边的控件
annoView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
// 传递模型
annoView.annotation = annotation;
// 设置图片
zidingyiAnnotation *tuangouAnno = annotation;
annoView.image = [UIImage imageNamed:tuangouAnno.icon];//这个icon是自定义的大头针模型中的数据
return annoView;
}
不过MKAnnotationView有一个子类MKPinAnnotationView,在iOS9之前多了两个属性
@property (nonatomic) BOOL animatesDrop;//用来设置大头针显示的时候是否是从天而降
@property (nonatomic) MKPinAnnotationColor pinColor NS_DEPRECATED(10_9, 10_11, 3_0, 9_0, "Use pinTintColor instead");
但是注意这个pinColor在iOS9中已经被废弃,被pinTintColor代替用来设置大头针的颜色
@property (NS_NONATOMIC_IOSONLY, strong, null_resettable) UIColor *pinTintColor NS_AVAILABLE(10_11, 9_0) UI_APPEARANCE_SELECTOR;
+ (UIColor *)redPinColor NS_AVAILABLE(10_11, 9_0);
+ (UIColor *)greenPinColor NS_AVAILABLE(10_11, 9_0);
+ (UIColor *)purplePinColor NS_AVAILABLE(10_11, 9_0);
五.导航画线
1.确定需要画线的两个地点,利用CLGeocoder中的(geocodeAddressString: completionHandler:)方法编码出两个地点的地理位置信息(CLPlacemark);
2.查找路线(设置起点,终点,并且计算路线)
3.绘制路线(绘制好的路线,利用addOverlay:方法添加地图上的路线,这个方法会调用mapView的一个代理方法来绘制线路)
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay//这里面设置路线的显示样式
下面是一个绘制路线的示例DEMO:
self.mapView.delegate = self;
NSString *address1 = @"北京";
NSString *address2 = @"广州";
[self.geocoder geocodeAddressString:address1 completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) return;
CLPlacemark *fromPm = [placemarks firstObject];
[self.geocoder geocodeAddressString:address2 completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) return;
CLPlacemark *toPm = [placemarks firstObject];
[self addLineFrom:fromPm to:toPm];
}];
}];
/**
* 添加导航的线路
*
* @param fromPm 起始位置
* @param toPm 结束位置
*/
- (void)addLineFrom:(CLPlacemark *)fromPm to:(CLPlacemark *)toPm
{
// 1.添加2个大头针
HTAnnotation *fromAnno = [[HTAnnotation alloc] init];
fromAnno.coordinate = fromPm.location.coordinate;
fromAnno.title = fromPm.name;
[self.mapView addAnnotation:fromAnno];
HTAnnotation *toAnno = [[HTAnnotation alloc] init];
toAnno.coordinate = toPm.location.coordinate;
toAnno.title = toPm.name;
[self.mapView addAnnotation:toAnno];
// 2.查找路线
// 方向请求
MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
// 设置起点
MKPlacemark *sourcePm = [[MKPlacemark alloc] initWithPlacemark:fromPm];
request.source = [[MKMapItem alloc] initWithPlacemark:sourcePm];
// 设置终点
MKPlacemark *destinationPm = [[MKPlacemark alloc] initWithPlacemark:toPm];
request.destination = [[MKMapItem alloc] initWithPlacemark:destinationPm];
// 方向对象
MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
// 计算路线
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
NSLog(@"总共%d条路线", response.routes.count);
// 遍历所有的路线
for (MKRoute *route in response.routes) {
// 添加路线遮盖
[self.mapView addOverlay:route.polyline];
}
}];
}
#pragma mark - MKMapViewDelegate
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
renderer.strokeColor = [UIColor redColor];
return renderer;
}