在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
。
目前就寫這麼多,歡迎你們提意見和建議。