使用MKMapView在App中嵌入地图

MapKit提供一系列接口,能够直接在View或者Window中嵌入地图,可使用基础的功能。从iOS 5.1以后,MapKit再也不使用Google地图服务,而改用高德地图。html

理解地图几何学

MapView包含了一个平面化的地球。为了更加有效地使用地图,你应该理解如何在MapView中指定一个点而且能够把这个点对应到地球表面上的一个点。若是想在地图上添加自定义的内容,好比运动轨迹等,那么理解地图坐标系是很重要的。ios

地图坐标系

MapKit使用了墨卡托投影,如上图所示。球体坐标被映射到圆柱表面,展开后就生成一个平面地图。
MapKit支持3中基本坐标系来肯定地图上的一点:git

  • map coordinates :使用latitude(纬度)和longitude(经度)表示地表上的一点。以地球为模型,纬线所在平面是和赤道所在平面平行,经线所在平面和赤道所在平面垂直。根据咱们平时所说的东经西经南纬北纬也能够分辨出。 Map coordinate是肯定地表位置的主要方法。你能够用CLLocationCoordinate2D结构体表示一个map coordinate值, 用MKCoordinateSpanMKCoordinateRegion表示一个区域。当须要存储真实位置数据时,使用map coordinate是最好的选择。Core Location也使用map coordinate。
  • map points :墨卡托投影中使用的坐标。使用Map points能够大大简化相关计算。当须要在地图上加入自定义的覆盖物,须要计算形状和位置,这时,可使用map point。使用MKMapPoint结构体表示一个map point值,使用MKMapSizeMKMapRect结构体表示一个区域。
  • points :它是view对象的坐标系的图形单元。在地图上绘制自定义view时,Map points 和 map coordinate 必须先转换成 points。使用CGPoint表示一个point,使用CGSize和CGRect表示一个区域。

注:CGPoint、CGSize和CGRect是咱们熟知的结构体,因此理解map coordinate和map point相关的结构体不会太难。数组

不一样坐标系之间的转换

  • Map Coordinates -> Points
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view
- (CGRect)convertRegion:(MKCoordinateRegion)region toRectToView:(UIView *)view
  • Map Coordinates -> Map Points
MKMapPoint MKMapPointForCoordinate ( CLLocationCoordinate2D coordinate );
  • Map Points -> Map Coordinates
CLLocationCoordinate2D MKCoordinateForMapPoint ( MKMapPoint mapPoint );
MKCoordinateRegion MKCoordinateRegionForMapRect ( MKMapRect rect );
  • Map Points -> Points
//iOS 7.0以后,这两个方法定义在`MKOverLayRenderer`类中,用来代替`MKOverlayView`。
- (CGPoint)pointForMapPoint:(MKMapPoint)mapPoint
- (CGRect)rectForMapRect:(MKMapRect)mapRect
  • Points -> Map Coordinates
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view
- (MKCoordinateRegion)convertRect:(CGRect)rect toRegionFromView:(UIView *)view
  • Points -> Map Points
//iOS 7.0以后,这两个方法定义在`MKOverLayRenderer`类中,用来代替`MKOverlayView`。
- (MKMapPoint)mapPointForPoint:(CGPoint)point
- (MKMapRect)mapRectForRect:(CGRect)rect

添加一个MapView到UI中

注意:在编译选项Build Phases -> Link Binary With Libraries中加入MapKit.framework,而后在源文件中#import <MapKit/MapKit.h> 服务器

MKMapView是一个功能齐全的接口,支持显示地区数据、响应用户操做、根据App支持自定义。__永远不要给它添加子类。__只须要直接嵌入到UI中就行了。它是有delegate的。它把全部相关的交互发送给delegate,以便于delegate可以作出正确的处理。 网络

你能够经过代码或者IB添加一个MapView到App中:app

  • 若是使用IB,只须要拖一个MKMapView到Storyboard中就行了。
  • 若是使用代码的话,先建立一个MKMapView实例,使用initWithFrame:方法进行初始化,而后使用addSubView:添加到视图层级中。

由于MKMapView是一个View,因此你能够像操做其余View那样操做它。好比改变它在视图中的Size或者Position,设置autoresizing,给它添加subView等。map view自身是不透明的容器。你添加的全部subView都是由它们自身的frame属性肯定所在位置,不会随着地图的滑动而随之滑动。你若是但愿随着地图的滑动而滑动的话,那么就必须使用annotations(注解)或者overlays(覆盖)。 ide

一个新建的Map View仅仅用来显示地图数据,接收用户交互。默认的,一个标准的地图使用3D视角,能够倾斜、旋转,并且还会显示方向。能够经过改变mapType属性,设置显示卫星地图或者卫星图和地图数据混合的地图。还能够经过rotateEnablepitchEnablezoomEnablescrollEnable属性限制用户的操做。若是须要响应交互,那么就使用MapView中的delegateui

设置地图的属性

MapView有一些能够进行设置的属性。好比,设置地图的可见区域、是否以3D方式呈现、是否容许用于交互等等。spa

设置地图的显示部分

首先,看一下MKCoordinateRegion结构体,定义以下:

typedef struct {
    CLLocationCoordinate2D center;
    MKCoordinateSpan span;
} MKCoordinateRegion;

在MKCoordinateRegion结构体中,__span__(跨度)定义了以当前经纬度为中心,显示在MapView中的地图的大小,也就是当前地图的缩放程度。span的表达虽然近似于矩形的宽和高度,可是由于使用map coordinate,所以是以度、分、秒为单位的。两条经线之间的距离会随着纬度的变化而变化。在赤道,经度1度的距离大约有111千米,而在极点,经度1度的距离却为0。若是须要使用来表示span的话,使用MKCoordinateRegionMakeWithDistance方法,就能够用代替

直接赋给region的值一般和最终存储到region的值是不一样的。在地图呈现的时候,系统会自动对地图进行缩放调整,以确保地图彻底显示在mapView的frame中。

给你们展现一个例子:

CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake(30.283307, 120.115352);
MKCoordinateSpan span = MKCoordinateSpanMake(0.005, 0.005);
MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
self.mapView.region = region;

NSLog(@"span.latitudeDelta:%f",self.mapView.region.span.latitudeDelta);
NSLog(@"span.longitudeDelta:%f",self.mapView.region.span.longitudeDelta);

控制台输出:

2016-03-10 18:33:30.483 MapKitDemo[11028:1610056] span.latitudeDelta:0.005000
//span的longitudeDelta的值和我初始化时赋的0.005的值有所误差,这就是系统自动调整所致
2016-03-10 18:33:30.483 MapKitDemo[11028:1610056] span.longitudeDelta:0.005790

MKMapView类中还有一个regionThatFits:方法。在官方文档中有这样一句话:

You can use the regionThatFits: method to determine the region that will actually be set by the map.

意思是说能够用regionThatFits:方法获得被地图设置的真正的region的值。

缩放和平移

缩放和平移地图时,都会改变地图的显示区域,即改变region的值。

  • 平移。改变mapView的centerCoordinate属性或者camera的值实现地图的平移,或者调用setCenterCoordinate:Animated:setCamera:Animated:方法。
CLLocationCoordinate2D mapCenter = myMapView.centerCoordinate;
mapCenter = [myMapView convertPoint:
               CGPointMake(1, (myMapView.frame.size.height/2.0))
               toCoordinateFromView:myMapView];
[myMapView setCenterCoordinate:mapCenter animated:YES];
  • 缩放。改变region的值或者调用setRegion:Animated:方法。在3D地图中,改变camera的高度。放大地图,longitudeDelta和latitudeDelta的值变小。缩小地图,longitudeDelta和latitudeDelta的值变大。就是和相机的聚焦是一个道理。
MKCoordinateRegion theRegion = myMapView.region;
 
// 放大
theRegion.span.longitudeDelta *= 2.0;
theRegion.span.latitudeDelta *= 2.0;
[myMapView setRegion:theRegion animated:YES];

// 缩小
theRegion.span.longitudeDelta /= 2.0;
theRegion.span.latitudeDelta /= 2.0;
[myMapView setRegion:theRegion animated:YES];

显示用户当前位置

在MKMapView中,能够显示用户当前位置。只须要设置showsUserLocation属性的值为YES。MKMapView会经过Core Location来肯定用户当前位置,而后在那里加上一个蓝色的圆点,做为标注。这就是咱们所说的Annotation,用MKUserLocation类来表示。

MKUserLocation类就是用来显示用户当前位置的特殊标注。你不能给这个类建立实例,而是经过检索MKMapView对象的userLocation属性(它是一个数组类型的数据)。

若是想在用户当前位置显示自定义的Annotation,须要实现mapView:viewForAnnotation:方法。这个方法的返回值是UIView。若是返回nil,那么就会使用系统默认的标注样式。

注意:即便showsUserLocation被设置成YES,MapView也不会将用户当前位置显示在地图中央。showsUserLocation这个属性只是标识是否在用户当前位置显示标注,也就是那个蓝色的点。若是想把用户当前位置显示在地图中央,则须要设置userTrackingMode属性。

若是在mapView初始化时,给region属性赋值的话,mapView会优先显示region属性对应的区域,而后将用户当前位置显示在mapView中央。而若是在showsUserLocation设为YES后,再次设置region的值,则依旧会显示region所对应的区域。

代理方法

为了能响应代理方法,要遵循MKMapViewDelegate,而且设置mapView的delegate属性。

响应region变化

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated

在用户对地图进行缩放或者平移操做时,这两个方法会被频繁调用。所以,这两个方法的实现尽量轻量化,避免影响用户滑动地图的体验。

请求地图数据

咱们在使用地图时,常常会发现有些地方只显示的网格。这些网格就是因为当前请求到的数据不足以显示这个部分的地图数据而出现的。而请求到地图数据以碎片的方式渲染到mapView上。

// 开始从服务器请求地图数据时调用
- (void)mapViewWillStartLoadingMap:(MKMapView *)mapView
// 结束请求地图数据时调用
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView
// 因为设备的网络或其余缘由致使请求数据失败时调用
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
// 开始渲染地图时调用
- (void)mapViewWillStartRenderingMap:(MKMapView *)mapView
// 结束渲染地图时调用,当全部碎片都成功渲染到mapView时,fullyRendered的值为YES,不然为NO
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered

mapView在显示地图数据时,首先会显示region设置的区域,这时可以看到的只是网格,而后根据region的值请求相应的数据,以后将请求到数据显示到mapView。所以,若是目前介绍的5个方法同时实现的话,被调用的顺序应该是:regionWillChange -> regionDidChange -> startRenderingMap -> startLoadingMap -> stopLoadingMap -> stopRenderingMap。

跟踪用户位置

// 当showsUserLocation的值设为YES时,会被调用
- (void)mapViewWillStartLocatingUser:(MKMapView *)mapView
// 当showsUserLocation的值设为NO时,会被调用
- (void)mapViewDidStopLocatingUser:(MKMapView *)mapView
// 当showsUserLocation被设为YES,且用户位置更新后,会被调用。
// 当userTrackingMode被设为MKUserTrackingModeFollowWithHeading,且设备所指方向改变时,也会被调用
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
// 尝试定位失败时,会被调用。error包含失败的缘由。
- (void)mapView:(MKMapView *)mapView didFailToLocateUserWithError:(NSError *)error
// 当userTrackingMode值改变时,会被调用
- (void)mapView:(MKMapView *)mapView didChangeUserTrackingMode:(MKUserTrackingMode)mode animated:(BOOL)animated

管理Annotation

- (MKAnnotationView *)mapView:(MKMapView *)mapView
            viewForAnnotation:(id<MKAnnotation>)annotation

这个方法的做用和tableView:cellForRowAtIndexPath:是类似的。它会返回MKAnnotationView类型的值。若是返回nil或者没有实现这个方法的话,会使用系统默认的标注样式。

不用每一次调用这个方法都建立一个新的MKAnnotationView,而是经过MKMapViewdequeueReusableAnnotationViewWithIdentifier:方法进行复用,若是不存在对应的annotation时,再建立一个新的MKAnnotationView对象。不管如何,都须要在这个方法里对MKAnnotationView的对象的属性进行设置,以显示须要的内容。

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views

这个方法容易理解。在全部annotation显示完毕后调用它。

- (void)mapView:(MKMapView *)mapView
 annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control

说道这个方法就不得不说一下MKAnnotationView的calloutView。当点击annotation后,弹出的View叫作calloutView。若是calloutView是继承自UIControl的类,好比UIButton,在它被点击以后就会调用这个方法。而若是calloutView不是继承自UIControl的类,那么就不会调用这个方法。

写了一个简单的例子。我从新写了一个MKAnnotationView,一个蓝色的点,来显示用户当前位置。它的leftCalloutAccessoryView是一个红色的按钮。当点击红色按钮时,`

  • mapView:annotationView:calloutAccessoryControlTapped:被调用,输出AnnotationView's calloutView was tapped.`。

image

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
    NSString *identifier = @"annotationView";
    MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
    if (annotationView == nil) {
        annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
    }
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 10, 10)];
    [button setBackgroundColor:[UIColor redColor]];
    button.layer.cornerRadius = 5;
    annotationView.backgroundColor = [UIColor blueColor];
    annotationView.leftCalloutAccessoryView = button;
    [annotationView setFrame:CGRectMake(0, 0, 15, 15)];
    annotationView.layer.cornerRadius = 7.5f;
    annotationView.canShowCallout = YES;
    return annotationView;
}
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views {
    NSLog(@"AnnotationViews were added.");
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
    NSLog(@"AnnotationView's calloutView was tapped.");
}

选定Annotation

- (void)mapView:(MKMapView *)mapView
didSelectAnnotationView:(MKAnnotationView *)view
- (void)mapView:(MKMapView *)mapView
didDeselectAnnotationView:(MKAnnotationView *)view

当一个MKAnnotationView被选中或者被取消选中时,调用这两个方法。

调用系统中的地图App

在iOS 6以后,使用MKMapItem对象来打开地图App。这个类提供openMapsWithItems:launchOptions:类方法和openInMapsWithLaunchOptions:实例方法来打开地图,展现位置和方向。

参考文档:
Displaying Maps
MapKit Framework Reference

相关文章
相关标签/搜索