在MKMapView上添加标注能够方便用户更好地获取信息,与地图进行交互。标注分为两种,一种是Annotations
,一种是Overlays
。html
Annotations。标注由经纬度所肯定的一个点,好比用户当前位置,一个被指定的地址,或者一个被收藏的地点。ios
Overlays。标注由多点连成的线,一个或者多个相邻或不相邻的区域。好比路线、交通情况、或者某个地点的边界。数组
和MKMapView中的subView不一样,Annotations和Overlays会随着地图的移动而移动。app
Annotations能够是地图上一个点醒目地标注出来,而且能够提供这个地点一些简单的信息。你能够用Annotations来标注当前位置、指定的位置、或者被收藏的位置等等。能够在地图上用一组图片来分别标记这些位置,还能够经过calloutView显示基本信息和可操做的空间,好比连接到更详细的介绍页面。框架
下面这张图,使用了大头针
来标注一个指定的地点,而且经过calloutView
显示一些基本信息,以及一个点击后能够提供驾车导航的按钮
和点击后能够跳转获取更多信息的按钮
。ide
若是要定义一个Annotation,要经过下面两个类:atom
Annotation object ,遵循MKAnnotation
协议,管理Annotation相关的属性。url
Annotation View ,MKAnnotationView
类型,来绘制Annotation的样式。spa
MapKit已经提供一些标准样式的Annotations,好比上图所示的大头针。也能够自定义annotationView。不管是使用标准的仍是自定义的annotationView,你都不能使用addSubView:
的方法将他们添加到mapView上。而应该使用mapView的代理方法mapView:viewForAnnotation:
代理
按照如下步骤来实现和使用Annotations。假定已经添加了mapView。
用下面任意一种方法定义_Annotation object_。
用MKPointAnnotation
类实现一个简单的Annotation。用这个方法定义的Annotation object包含calloutView的title和subtitle属性。
自定义一个遵循MKAnnotation
协议的类。这个类能够包含任何你想包含的属性。
定义一个_Annotation View_。根据你的须要选择合适的方法。
若是使用系统提供的大头针做为标注,只须要建立一个MKPinAnnotationView
的实例便可。
若是使用一张静态图片,建立一个MKAnnotationView
的实例,给它的image
属性赋值便可。
若是上面两种方法已经没法知足你,那么就新建一个继承自MKAnnotation
类的子类,实现绘制代码。
实现mapView的代理方法mapView:viewForAnnotation:
。
在实现这个方法的时候,若是存在能够复用的annotationView,就直接使用。若是不存在,新建一个annotationView。若是须要显示多种类型的annotationView,根据annotaion
类型不一样,显示相应类型的annotationView。
这个方法让我想起tableView:cellForRowAtIndexPath:
。它们两个的实现方式很类似。
使用addAnnotation
或者addAnnotations:
方法,添加annotationView到mapView上。
不管被标注的位置是否在可见区域内,annotationView都会被添加到mapView上。若是但愿选择性地隐藏annotationView,你必须手动移除它们。
不管mapView的缩放比例是多少,AnnotationView都会以相同的大小显示。所以,当用户将地图比例缩小时,极可能会是AnnotationView会相互遮挡。为了解决这样的问题,能够根据缩放比例,添加或者移除annotationView。好比在一个天气应用中,当缩放比例小的时候,只显示省会城市的天气;当缩放比例变大的时候,能够逐渐显示出地级市、区县、乡镇的天气信息。
首先,让咱们来理解一下这两个类的做用以及它们的关系。
MKAnnotation类中定义了MKAnnotation
协议,这个协议定义了coordinate(必须实现的属性)、title和subtitle。coordinate属性的做用就是定义在哪一个点处显示MKAnnotationView。
因此,一个MKAnnotationView都会对应一个MKAnnotation对象,即它的annotation
属性。而MKAnnotation对象能够适用于多个MKAnnotationView。
MKAnnotationView是用来定义标注的样式。
CalloutView是当MKAnnotationView被选中后,弹出的View,用于呈现更多关于当前标注的位置的信息。默认状况下,它的title和subtitle由MKAnnotationView对象的annotation
属性定义。
接下来,分别介绍它们的用法。
若是仅仅须要关联一个位置的title,你只要用MKPointAnnotation
类做为Annotation对象就好了。若是想添加另外的信息,你须要自定义一个Annotation对象。全部的Annotation对象必须遵循MKAnnotation
协议。
一个自定义的Annotation对象必须包含coordinate
和其余你想要的属性。给出最简单的Annotation对象的定义。
@interface myCustomAnnotation : NSObject <MKAnnotation> { CLLocationCoordinate2D coordinate; } @property (readonly, nonatomic) CLLocationCoordinate2D coordinate; - (instancetype)initWithLocation:(CLLocationCoordinate2D)coord; // 其余方法或者属性 @end
自定义的类必须实现coordinate
属性和一个给它赋值的初始化方法。(建议使用@synthesize,能够保证mapkit能够根据这个属性值的改变自动更新地图。)
@implementation myCustomAnnotation @synthesize coordinate; - (instancetype)initWithLocation:(CLLocationCoordinate2D)coord { self = [super init]; if (self != nil) { coordinate = coord; } return self; } @end
若是AnnotationView添加到mapView上以后,你手动地修改类中coordinate、title、subtitle属性的值,请务必发送一个通知。MapKit使用KVO检测这三个属性值的变化以在须要的时候更新地图。若是不发送,可能会致使位置的标注没有被正确显示。
使用系统提供的annotationView能够很轻松地标注地图。MKAnnotationView
定义了全部annotationView的基本行为。它的子类MKPinAnnotationView
用一张大头针的图片来标注一个位置。
也能够不经过继承,直接设置它的image
属性,来显示一张图片做为annotationView。这张图片是以被标注的位置为中心呈现的。若是不想显示在中心点,你可使用centerOffset
属性移动中心点。
举个栗子。建立一个自定义图片的MKAnnotationView,而且图片显示在经纬度的右下方。
MKAnnotationView* aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"MyCustomAnnotation"]; aView.image = [UIImage imageNamed:@"myimage.png"]; aView.centerOffset = CGPointMake(10, -20);
能够在代理方法mapView:viewForAnnotation:
中建立标准的AnnotationView。
若是静态图片不能知足你的需求,你就能够经过继承MKAnnotationView
来自定义annotationView。
重写drawRect:
方法,从新定义样式。
当重写drawRect:
方法时,务必保证annotationView的frame非零,以确保在地图上是可见的。由于默认的初始化方法会用image
属性的图片的frame做为annotationView的frame。
给一个简单的例子,重写了- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
方法。
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; if (self) { CGRect myFrame = self.frame; myFrame.size.width = 40; myFrame.size.height = 40; self.frame = myFrame; self.backgroundColor = [UIColor blueColor]; self.opaque = NO; } return self; }
当须要添加annotationView时,调用代理方法mapView:viewForAnnotation:
。若是没有实现
这个方法或者总返回nil
的话,系统就会使用默认的annotationView。若是不想使用系统默认的,那就重写这个方法,而后返回MKAnnotationView
对象。
在每次建立新的annotationView时,总要检查是否存在可复用的View。mapView的dequeueReusableAnnotationViewWithIdentifier:
方法能够获取到能够复用的View。若是返回了nil
,那么建立一个新的annotationView。若是没有返回nil
,那么将它的属性值换掉,而后赋给annotationView。__不管是哪一种状况,都要把方法中annotation
参数赋给annotationView.annotation
。__
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation { // 若是标注的是用户当前位置,则直接返回nil。 if ([annotation isKindOfClass:[MKUserLocation class]]) return nil; // 处理自定义的annotation。 if ([annotation isKindOfClass:[MyCustomAnnotation class]]) { // 首先尝试复用已存在的MKPinAnnotationView。 MKPinAnnotationView *pinView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotationView"]; if (!pinView) { // 没有能够复用的View,新建一个。 pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"CustomPinAnnotationView"]; pinView.pinColor = MKPinAnnotationColorRed; pinView.animatesDrop = YES; pinView.canShowCallout = YES; // 若是有的话,能够经过设置accessoryView定义callout。 } else pinView.annotation = annotation; return pinView; } return nil; }
Callout是在annotationView被选中时弹出。这时,AnnotationView的selected
属性为YES。你能够经过setSelected:
方法设置selected属性,手动控制CalloutView的显示和消失。
一样地,它既能够是系统提供的标准View,也能够是自定义View。一个标准的callout会显示标注的title,此外,它还能够显示subtitle、image和一个UIControl对象。若是想自定义callout,给annotationView添加自定义的subView,而后重写hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
方法响应用户事件。
使用标准的callout是显示自定义内容最容易的方法。以下图所示,在callout中添加了图片和详情按钮。
接下来给出代码,如何实现修改callout样式。
// 假设这个annotationView已经添加到mapView上了。 - (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation { // 首先尝试重用pin view。(代码没有贴出来,请参照上一段代码)。 // 若是没有能够重用的View,则新建一个对象。 MKPinAnnotationView *customPinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:BridgeAnnotationIdentifier]; customPinView.pinColor = MKPinAnnotationColorPurple; customPinView.animatesDrop = YES; customPinView.canShowCallout = YES; // 添加右边的详情按钮。 UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; // 由于没有页面跳转,因此Target和action参数设为nil。 [rightButton addTarget:nil action:nil forControlEvents:UIControlEventTouchUpInside]; customPinView.rightCalloutAccessoryView = rightButton; // 在callout左边添加自定义图片。 UIImageView *myCustomImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"MyCustomImage.png"]]; customPinView.leftCalloutAccessoryView = myCustomImage; return customPinView; }
在iOS开发中,实现mapView:annotationView:calloutAccessoryControlTapped:
代理方法来响应callout的control(必须是继承自UIControl
)的点击事件。在实现这个方法的时候,经过AnnotationView的identifier
来分别那个AnnotationView的callout的control被点击了。
当自定义callout时,须要多作一些工做,保证callout可以正常显示和消失。
建立一个UIView
的子类。须要重写drawRect:
方法。
建立一个ViewController,初始化callout,执行按钮的点击事件。
在AnnotationView中,实现hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
方法响应在callout边界内的点击事情。
在AnnotationView中,实现setSelected:animated:
方法。将自定义的callout做为annotationView的subView。当用户点击annotationView时,显示callout。若是callout已经显示,那么在setSelected:animated:
中应该让callout消失,并从subViews
中移除。
在annotationView的initWithAnnotation:
方法中,将canShowCallout
设为NO
,防止用户点击annotationView弹出系统的callout。
上文说起了显示多个annotationView会形成的不良后果。在缩放程度太小的时候,多个annotationView会由于离得太近而乱成一堆,用户没法清晰地分辨。而解决的方案就是经过缩放比例,改变显示的annotationView的个数。
调用mapView:regionWillChangeAnimated:
和mapView:regionDidChangeAnimated:
方法检测缩放程度。当它变化时,根据须要添加或移除一部分annotationView。也许,还须要考虑其余因素(好比用户当前位置)决定它们的去留。
Overlays可让咱们在地图上标记处任意的区域。和Annotations不一样,Overlays是根据多个坐标定义的。根据这些坐标,能够将它们连成线、矩形、圆或者其余不规则的图形,同时能够给这些图形填充颜色。利用Overlays,咱们能够显示路况信息、地点的边界、路线等等。
和显示Annotation同样,显示Overlays一样要定义两个对象:
overlay object。遵循MKOverlay协议,管理overlay相关的坐标点。
overlayRender。MKOverlayRender类的对象,用来定义显示在地图上的overlay。
__在iOS 7.0以后,使用MKOverlayRender代替MKOverlayView。前者提供了和MKOverlayView相同的功能,但更加轻量、高效。
MKOverlay和MKOverlayRender类的做用,请对比MKAnnotation和MKAnnotationView。
定义一个MKOverlay对象。
直接使用MKCircle
、MKPolygon
或者MKPolyline
类。
继承MKShape
或者MKMultiPoint
类。
使用任何遵循MKOverlay
协议的类。
定义Overlay Render。
对一些标准的形状,好比圆形、多边形等,使用MKCircleRender
、MKPolygonRender
或者MKPolylineRender
。经过设置这些类的属性能够获得不一样的样式。
对于继承MKShape
的自定义形状,定义一个MKOverlayPathRender
的子类呈现它们。
对于其余自定义的overlay,定义MKOverlayRenderer
类的子类,实现本身的绘制方法。
实现mapView
的mapView:rendererForOverlay:
代理方法。
使用addOverlay:
方法,将其添加到mapView上。
和annotation不一样的是,overlay会随着地图的缩放而缩放。由于overlay表示地图上的边界、路线等信息。
若是想标注显示地图上的某个区域,使用标准的Overlay类是最简单的方法。标准的Overlay类包括MKCircle
、MKPolypon
、MKPolyline
,配合MKCircleRender
、MKPolygonRender
、MKPolylineRender
类将它们显示到mapView上。
定义一个MKPolyline对象。MKPolyline有两个初始化方法,分别是使用CLLocationCoordinate
类型的数组
和MKMapPoint
类型的数组
,count
参数表示数组中所包含的元素个数。
CLLocationCoordinate2D points[2]; points[0] = CLLocationCoordinate2DMake(30.000000, 120.000000); points[1] = CLLocationCoordinate2DMake(40.000000, 130.000000); MKPolyline *polyline = [MKPolyline polylineWithCoordinates:points count:2]; [self.mapView addOverlay:polyline];
要把overlay显示到mapView上,必须实现mapView的mapView:rendererForOverlay:
代理方法,返回MKOverlayRender
类的对象。
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay { if ([overlay isKindOfClass:[MKPolyline class]]) { MKPolylineRenderer *polylineRender = [[MKPolylineRenderer alloc] initWithPolyline:(MKPolyline *)overlay]; [polylineRender setNeedsDisplay]; polylineRender.fillColor = [UIColor redColor]; polylineRender.strokeColor = [UIColor redColor]; polylineRender.lineWidth = 1.0f; return polylineRender; } return nil; }
注意:若是是用MKPolylineRender的话,使用strokeColor
属性设置其颜色,而不是fillColor
属性。
经过MKLocalSearch
、MKLocalSearchRequest
,咱们能够实现对地图的搜索。
先来一段代码。结合UISearchBar和MKMapKit,将搜索的内容在地图上标注出来。在每次从新搜索的时候移除已经添加的MKAnnotation。搜索的结果以MKMapItem
类型的数组给出,能够取出位置相关的信息,好比名称,经纬度,url等等。
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { [self.mapView removeAnnotations:self.annotationArray]; self.searchRequest.naturalLanguageQuery = self.searchBar.text; self.localSearch = [[MKLocalSearch alloc] initWithRequest:self.searchRequest]; [self.localSearch startWithCompletionHandler:^(MKLocalSearchResponse * _Nullable response, NSError * _Nullable error) { self.resultArray = [NSMutableArray arrayWithArray:response.mapItems]; self.annotationArray = [NSMutableArray array]; for (MKMapItem *mapItem in self.resultArray) { self.placeAnnotation = [[PlaceAnnotation alloc] init]; self.placeAnnotation.coordinate = mapItem.placemark.location.coordinate; self.placeAnnotation.title = mapItem.name; self.placeAnnotation.url = mapItem.url; [self.annotationArray addObject:self.placeAnnotation]; [self.mapView addAnnotation:self.placeAnnotation]; } }]; [self.searchBar resignFirstResponder]; [self.resultArray removeAllObjects]; }
结合以前的两篇文章,我本身算是把MapKit和CoreLocation的基本用法理了一遍。从看官方文档到从Stackoverflow查找问题解决方法,花了挺长时间的。
最后总结一下使用MapKit和CoreLocation时须要注意的点:
若是App中须要用到定位服务,
首先要添加MapKit和CoreLocation这两个系统框架
其次根据须要在info.plist中加入NSLocationWhenInUseUsageDescription
和NSLocationAlwaysUsageDescription
两个字段
最后就是相应地调用requestWhenInUseAuthorization
或者requestAlwaysAuthorization
方法请求用户受权。
为了保险起见,在每一次调用startUpdatingLocation
方法前,检查App是否已经获取到定位服务的权限。
使用定位服务是很耗电的,因此每次在退出有mapView的页面以前,调用一次stopUpdatingLocation
,可能对节省电量有帮助。
在使用MKPolylineRender时,使用strokeColor
属性给其设置颜色。而不是fillColor
。
目前就写这么多,欢迎你们提意见和建议。