使用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

相關文章
相關標籤/搜索