如今不少社交、電商、團購應用都引入了地圖和定位功能,彷佛地圖功能再也不是地圖應用和導航應用所特有的。的確,有了地圖和定位功能確實讓咱們的生活更加豐富多彩,極大的改變了咱們的生活方式。例如你到了一個陌生的地方想要查找附近的酒店、超市等就能夠打開軟件搜索周邊;相似的,還有不少團購軟件能夠根據你所在的位置自動爲你推薦某些商品。總之,目前地圖和定位功能已經大量引入到應用開發中。今天就和你們一塊兒看一下iOS如何進行地圖和定位開發。html
要實現地圖、導航功能,每每須要先熟悉定位功能,在iOS中經過Core Location框架進行定位操做。Core Location自身能夠單獨使用,和地圖開發框架MapKit徹底是獨立的,可是每每地圖開發要配合定位框架使用。在Core Location中主要包含了定位、地理編碼(包括反編碼)功能。數組
定位是一個很經常使用的功能,如一些地圖軟件打開以後若是用戶容許軟件定位的話,那麼打開軟件後就會自動鎖定到當前位置,若是用戶手機移動那麼當前位置也會跟隨着變化。要實現這個功能須要使用Core Loaction中CLLocationManager類,首先看一下這個類的一些主要方法和屬性:緩存
類方法 | 說明 |
+ (BOOL)locationServicesEnabled; | 是否啓用定位服務,一般若是用戶沒有啓用定位服務能夠提示用戶打開定位服務 |
+ (CLAuthorizationStatus)authorizationStatus; | 定位服務受權狀態,返回枚舉類型: kCLAuthorizationStatusNotDetermined: 用戶還沒有作出決定是否啓用定位服務 kCLAuthorizationStatusRestricted: 沒有得到用戶受權使用定位服務,可能用戶沒有本身禁止訪問受權 kCLAuthorizationStatusDenied :用戶已經明確禁止應用使用定位服務或者當前系統定位服務處於關閉狀態 kCLAuthorizationStatusAuthorizedAlways: 應用得到受權能夠一直使用定位服務,即便應用不在使用狀態 kCLAuthorizationStatusAuthorizedWhenInUse: 使用此應用過程當中容許訪問定位服務 |
屬性 | 說明 |
desiredAccuracy | 定位精度,枚舉類型: kCLLocationAccuracyBest:最精肯定位 |
distanceFilter | 位置信息更新最小距離,只有移動大於這個距離才更新位置信息,默認爲kCLDistanceFilterNone:不進行距離限制 |
對象方法 | 說明 |
startUpdatingLocation | 開始定位追蹤,開始定位後將按照用戶設置的更新頻率執行-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;方法反饋定位信息 |
stopUpdatingLocation | 中止定位追蹤 |
startUpdatingHeading | 開始導航方向追蹤 |
stopUpdatingHeading | 中止導航方向追蹤 |
startMonitoringForRegion: | 開始對某個區域進行定位追蹤,開始對某個區域進行定位後。若是用戶進入或者走出某個區域會調用- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region和- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region代理方法反饋相關信息 |
stopMonitoringForRegion: | 中止對某個區域進行定位追蹤 |
requestWhenInUseAuthorization | 請求得到應用使用時的定位服務受權,注意使用此方法前在要在info.plist中配置NSLocationWhenInUseUsageDescription |
requestAlwaysAuthorization | 請求得到應用一直使用定位服務受權,注意使用此方法前要在info.plist中配置NSLocationAlwaysUsageDescription |
代理方法 | 說明 |
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations; | 位置發生改變後執行(第一次定位到某個位置以後也會執行) |
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading; |
導航方向發生變化後執行 |
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region |
進入某個區域以後執行 |
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region |
走出某個區域以後執行 |
iOS 8 還提供了更加人性化的定位服務選項。App 的定位服務再也不僅僅是關閉或打開,如今,定位服務的啓用提供了三個選項,「永不」「使用應用程序期間」和「始終」。同時,考慮到能耗問題,若是一款 App 要求始終能在後臺開啓定位服務,iOS 8 不只會在首次打開 App 時主動向你詢問,還會在平常使用中彈窗提醒你該 App 一直在後臺使用定位服務,並詢問你是否繼續容許。在iOS7及之前的版本,若是在應用程序中使用定位服務只要在程序中調用startUpdatingLocation方法應用就會詢問用戶是否容許此應用是否容許使用定位服務,同時在提示過程當中能夠經過在info.plist中配置經過配置Privacy - Location Usage Description告訴用戶使用的目的,同時這個配置是可選的。
可是在iOS8中配置配置項發生了變化,能夠經過配置NSLocationAlwaysUsageDescription或者NSLocationWhenInUseUsageDescription來告訴用戶使用定位服務的目的,而且注意這個配置是必須的,若是不進行配置則默認狀況下應用沒法使用定位服務,打開應用不會給出打開定位服務的提示,除非安裝後本身設置此應用的定位服務。同時,在應用程序中須要根據配置對requestAlwaysAuthorization或locationServicesEnabled方法進行請求。因爲本人機器已經更新到最新的iOS8.1下面的內容主要針對iOS8,使用iOS7的朋友須要稍做調整。 app
// // KCMainViewController.m // CoreLocation // // Created by Kenshin Cui on 14-03-27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>@interface KCMainViewController ()<CLLocationManagerDelegate>{ CLLocationManager *_locationManager; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; //定位管理器 _locationManager=[[CLLocationManager alloc]init]; if (![CLLocationManager locationServicesEnabled]) { NSLog(@"定位服務當前可能還沒有打開,請設置打開!"); return; } //若是沒有受權則請求用戶受權 if ([CLLocationManager authorizationStatus]==kCLAuthorizationStatusNotDetermined){ [_locationManager requestWhenInUseAuthorization]; }else if([CLLocationManager authorizationStatus]==kCLAuthorizationStatusAuthorizedWhenInUse){ //設置代理 _locationManager.delegate=self; //設置定位精度 _locationManager.desiredAccuracy=kCLLocationAccuracyBest; //定位頻率,每隔多少米定位一次 CLLocationDistance distance=10.0;//十米定位一次 _locationManager.distanceFilter=distance; //啓動跟蹤定位 [_locationManager startUpdatingLocation]; } }#pragma mark - CoreLocation 代理#pragma mark 跟蹤定位代理方法,每次位置發生變化即會執行(只要定位到相應位置)//能夠經過模擬器設置一個虛擬位置,不然在模擬器中沒法調用此方法-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ CLLocation *location=[locations firstObject];//取出第一個位置 CLLocationCoordinate2D coordinate=location.coordinate;//位置座標 NSLog(@"經度:%f,緯度:%f,海拔:%f,航向:%f,行走速度:%f",coordinate.longitude,coordinate.latitude,location.altitude,location.course,location.speed); //若是不須要實時定位,使用完即便關閉定位服務 [_locationManager stopUpdatingLocation]; } @end
注意:框架
1.定位頻率和定位精度並不該當越精確越好,須要視實際狀況而定,由於越精確越耗性能,也就越費電。ide
2.定位成功後會根據設置狀況頻繁調用-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations方法,這個方法返回一組地理位置對象數組,每一個元素一個CLLocation表明地理位置信息(包含經度、緯度、海報、行走速度等信息),之因此返回數組是由於有些時候一個位置點可能包含多個位置。函數
3.使用完定位服務後若是不須要實時監控應該當即關閉定位服務以節省資源。
4.除了提供定位功能,CLLocationManager還能夠調用startMonitoringForRegion:方法對指定區域進行監控。
除了提供位置跟蹤功能以外,在定位服務中還包含CLGeocoder類用於處理地理編碼和逆地理編碼(又叫反地理編碼)功能。
地理編碼:根據給定的位置(一般是地名)肯定地理座標(經、緯度)。
逆地理編碼:能夠根據地理座標(經、緯度)肯定位置信息(街道、門牌等)。
CLGeocoder最主要的兩個方法就是- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;和- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;,分別用於地理編碼和逆地理編碼。下面簡單演示一下:
// // KCMainViewController.m // CoreLocation // // Created by Kenshin Cui on 14-03-27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>@interface KCMainViewController ()<CLLocationManagerDelegate>{ CLGeocoder *_geocoder; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; _geocoder=[[CLGeocoder alloc]init]; [self getCoordinateByAddress:@"北京"]; [self getAddressByLatitude:39.54 longitude:116.28]; }#pragma mark 根據地名肯定地理座標 -(void)getCoordinateByAddress:(NSString *)address{ //地理編碼 [_geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) { //取得第一個地標,地標中存儲了詳細的地址信息,注意:一個地名可能搜索出多個地址 CLPlacemark *placemark=[placemarks firstObject]; CLLocation *location=placemark.location;//位置 CLRegion *region=placemark.region;//區域 NSDictionary *addressDic= placemark.addressDictionary;//詳細地址信息字典,包含如下部分信息 // NSString *name=placemark.name;//地名 // NSString *thoroughfare=placemark.thoroughfare;//街道 // NSString *subThoroughfare=placemark.subThoroughfare; //街道相關信息,例如門牌等 // NSString *locality=placemark.locality; // 城市 // NSString *subLocality=placemark.subLocality; // 城市相關信息,例如標誌性建築 // NSString *administrativeArea=placemark.administrativeArea; // 州 // NSString *subAdministrativeArea=placemark.subAdministrativeArea; //其餘行政區域信息 // NSString *postalCode=placemark.postalCode; //郵編 // NSString *ISOcountryCode=placemark.ISOcountryCode; //國家編碼 // NSString *country=placemark.country; //國家 // NSString *inlandWater=placemark.inlandWater; //水源、湖泊 // NSString *ocean=placemark.ocean; // 海洋 // NSArray *areasOfInterest=placemark.areasOfInterest; //關聯的或利益相關的地標 NSLog(@"位置:%@,區域:%@,詳細信息:%@",location,region,addressDic); }]; }#pragma mark 根據座標取得地名 -(void)getAddressByLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude{ //反地理編碼 CLLocation *location=[[CLLocation alloc]initWithLatitude:latitude longitude:longitude]; [_geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *placemark=[placemarks firstObject]; NSLog(@"詳細信息:%@",placemark.addressDictionary); }]; } @end
iOS從6.0開始地圖數據再也不由谷歌驅動,而是改用自家地圖,固然在國內它的數據是由高德地圖提供的。這樣一來,若是在iOS6.0以前進行地圖開發的話使用方法會有所不一樣,基於目前的狀況其實使用iOS6.0以前版本的系統基本已經寥寥無幾了,全部在接下來的內容中不會再針對iOS5及以前版本的地圖開發進行介紹。
在iOS中進行地圖開發主要有兩種方式,一種是直接利用MapKit框架進行地圖開發,利用這種方式能夠對地圖進行精準的控制;另外一種方式是直接調用蘋果官方自帶的地圖應用,主要用於一些簡單的地圖應用(例如:進行導航覆蓋物填充等),沒法進行精確的控制。固然,本節重點內容仍是前者,後面的內容也會稍加提示。
用MapKit以前須要簡單瞭解一下MapKit中地圖展現控件MKMapView的的一些經常使用屬性和方法,具體以下表:
屬性 | 說明 |
userTrackingMode | 跟蹤類型,是一個枚舉: MKUserTrackingModeNone :不進行用戶位置跟蹤; MKUserTrackingModeFollow :跟蹤用戶位置; MKUserTrackingModeFollowWithHeading :跟蹤用戶位置而且跟蹤用戶前進方向; |
mapType | 地圖類型,是一個枚舉: MKMapTypeStandard :標準地圖,通常狀況下使用此地圖便可知足; MKMapTypeSatellite :衛星地圖; MKMapTypeHybrid :混合地圖,加載最慢比較消耗資源; |
userLocation | 用戶位置,只讀屬性 |
annotations | 當前地圖中的全部大頭針,只讀屬性 |
對象方法 | 說明 |
- (void)addAnnotation:(id <MKAnnotation>)annotation; | 添加大頭針,對應的有添加大頭針數組 |
- (void)removeAnnotation:(id <MKAnnotation>)annotation; | 刪除大頭針,對應的有刪除大頭針數組 |
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated; |
設置地圖顯示區域,用於控制當前屏幕顯示地圖範圍 |
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated; | 設置地圖中心點位置 |
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view; | 將地理座標(經緯度)轉化爲數學座標(UIKit座標) |
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view; | 將數學座標轉換爲地理座標 |
- (MKAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier; | 從緩存池中取出大頭針,相似於UITableView中取出UITableViewCell,爲了進行性能優化而設計 |
- (void)selectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated; | 選中指定的大頭針 |
- (void)deselectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated; | 取消選中指定的大頭針 |
代理方法 | 說明 |
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation ; | 用戶位置發生改變時觸發(第一次定位到用戶位置也會觸發該方法) |
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation ; | 顯示區域發生改變後觸發 |
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView; | 地圖加載完成後觸發 |
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation; | 顯示大頭針時觸發,返回大頭針視圖,一般自定義大頭針能夠經過此方法進行 |
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view | 點擊選中某個大頭針時觸發 |
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view | 取消選中大頭針時觸發 |
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay | 渲染地圖覆蓋物時觸發 |
在不少帶有地圖的應用中默認打開地圖都會顯示用戶當前位置,同時將當前位置標記出來放到屏幕中點方便用戶對周圍狀況進行查看。若是在iOS6或者iOS7中實現這個功能只須要添加地圖控件、設置用戶跟蹤模式、在-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation代理方法中設置地圖中心區域及顯示範圍。可是在iOS8中用法稍有不一樣:
1.因爲在地圖中進行用戶位置跟蹤須要使用定位功能,而定位功能在iOS8中設計發生了變化,所以必須按照前面定位章節中提到的內容進行配置和請求。
2.iOS8中不須要進行中心點的指定,默認會將當前位置設置中心點並自動設置顯示區域範圍。
瞭解以上兩點,要進行用戶位置跟蹤其實就至關簡單了,值得一提的是-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation這個代理方法。這個方法只有在定位(利用前面章節中的定位內容)到當前位置以後就會調用,之後每當用戶位置發生改變就會觸發,調用頻率至關頻繁。
在iOS開發中常常會標記某個位置,須要使用地圖標註,也就是你們俗稱的「大頭針」。只要一個NSObject類實現MKAnnotation協議就能夠做爲一個大頭針,一般會重寫協議中coordinate(標記位置)、title(標題)、subtitle(子標題)三個屬性,而後在程序中建立大頭針對象並調用addAnnotation:方法添加大頭針便可(之因此iOS沒有定義一個基類實現這個協議供開發者使用,多數緣由應該是MKAnnotation是一個模型對象,對於多數應用模型會稍有不一樣,例如後面的內容中會給大頭針模型對象添加其餘屬性)。
KCAnnotation.h
// // KCAnnotation.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import <Foundation/Foundation.h>#import <MapKit/MapKit.h>@interface KCAnnotation : NSObject<MKAnnotation> @property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *subtitle; @end
KCMainViewController.m
// // KCMainViewController.m // MapKit Annotation // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // 37.785834 -122.406417 // 39.92 116.39#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>#import "KCAnnotation.h"@interface KCMainViewController ()<MKMapViewDelegate>{ CLLocationManager *_locationManager; MKMapView *_mapView; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self initGUI]; }#pragma mark 添加地圖控件 -(void)initGUI{ CGRect rect=[UIScreen mainScreen].bounds; _mapView=[[MKMapView alloc]initWithFrame:rect]; [self.view addSubview:_mapView]; //設置代理 _mapView.delegate=self; //請求定位服務 _locationManager=[[CLLocationManager alloc]init]; if(![CLLocationManager locationServicesEnabled]||[CLLocationManager authorizationStatus]!=kCLAuthorizationStatusAuthorizedWhenInUse){ [_locationManager requestWhenInUseAuthorization]; } //用戶位置追蹤(用戶位置追蹤用於標記用戶當前位置,此時會調用定位服務) _mapView.userTrackingMode=MKUserTrackingModeFollow; //設置地圖類型 _mapView.mapType=MKMapTypeStandard; //添加大頭針 [self addAnnotation]; }#pragma mark 添加大頭針 -(void)addAnnotation{ CLLocationCoordinate2D location1=CLLocationCoordinate2DMake(39.95, 116.35); KCAnnotation *annotation1=[[KCAnnotation alloc]init]; annotation1.title=@"CMJ Studio"; annotation1.subtitle=@"Kenshin Cui's Studios"; annotation1.coordinate=location1; [_mapView addAnnotation:annotation1]; CLLocationCoordinate2D location2=CLLocationCoordinate2DMake(39.87, 116.35); KCAnnotation *annotation2=[[KCAnnotation alloc]init]; annotation2.title=@"Kenshin&Kaoru"; annotation2.subtitle=@"Kenshin Cui's Home"; annotation2.coordinate=location2; [_mapView addAnnotation:annotation2]; }#pragma mark - 地圖控件代理方法#pragma mark 更新用戶位置,只要用戶改變則調用此方法(包括第一次定位到用戶位置) -(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{ NSLog(@"%@",userLocation); //設置地圖顯示範圍(若是不進行區域設置會自動顯示區域範圍並指定當前用戶位置爲地圖中心點) // MKCoordinateSpan span=MKCoordinateSpanMake(0.01, 0.01); // MKCoordinateRegion region=MKCoordinateRegionMake(userLocation.location.coordinate, span); // [_mapView setRegion:region animated:true];} @end
運行效果:
在一些應用中系統默認的大頭針樣式可能沒法知足實際的需求,此時就須要修改大頭針視圖默認樣式。根據前面MapKit的代理方法不難發現- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;方法能夠返回一個大頭針視圖,只要實現這個方法並在這個方法中定義一個大頭針視圖MKAnnotationView對象並設置相關屬性就能夠改變默認大頭針的樣式。MKAnnotationView經常使用屬性:
屬性 | 說明 |
annotation | 大頭針模型信息,包括標題、子標題、地理位置。 |
image | 大頭針圖片 |
canShowCallout | 點擊大頭針是否顯示標題、子標題內容等,注意若是在- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;方法中從新定義大頭針默認狀況是沒法交互的須要設置爲true。 |
calloutOffset | 點擊大頭針時彈出詳情信息視圖的偏移量 |
selected | 是否被選中狀態 |
leftCalloutAccessoryView | 彈出詳情左側視圖 |
rightCalloutAccessoryView | 彈出詳情右側視圖 |
須要注意:
a.這個代理方法的調用時機:每當有大頭針顯示到系統可視界面中時就會調用此方法返回一個大頭針視圖放到界面中,同時當前系統位置標註(也就是地圖中藍色的位置點)也是一個大頭針,也會調用此方法,所以處理大頭針視圖時須要區別對待。
b.相似於UITableView的代理方法,此方法調用頻繁,開發過程當中須要重複利用MapKit的緩存池將大頭針視圖緩存起來重複利用。
c.自定義大頭針默認狀況下不容許交互,若是交互須要設置canShowCallout=true
d.若是代理方法返回nil則會使用默認大頭針視圖,須要根據狀況設置。
下面以一個示例進行大頭針視圖設置,這裏設置了大頭針的圖片、彈出視圖、偏移量等信息。
KCAnnotation.h
// // KCAnnotation.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import <Foundation/Foundation.h>#import <MapKit/MapKit.h>@interface KCAnnotation : NSObject<MKAnnotation> @property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *subtitle;#pragma mark 自定義一個圖片屬性在建立大頭針視圖時使用 @property (nonatomic,strong) UIImage *image; @end
KCMainViewController.m
// // KCMainViewController.m // MapKit Annotation // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // 37.785834 -122.406417 // 39.92 116.39#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>#import "KCAnnotation.h"@interface KCMainViewController ()<MKMapViewDelegate>{ CLLocationManager *_locationManager; MKMapView *_mapView; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self initGUI]; }#pragma mark 添加地圖控件 -(void)initGUI{ CGRect rect=[UIScreen mainScreen].bounds; _mapView=[[MKMapView alloc]initWithFrame:rect]; [self.view addSubview:_mapView]; //設置代理 _mapView.delegate=self; //請求定位服務 _locationManager=[[CLLocationManager alloc]init]; if(![CLLocationManager locationServicesEnabled]||[CLLocationManager authorizationStatus]!=kCLAuthorizationStatusAuthorizedWhenInUse){ [_locationManager requestWhenInUseAuthorization]; } //用戶位置追蹤(用戶位置追蹤用於標記用戶當前位置,此時會調用定位服務) _mapView.userTrackingMode=MKUserTrackingModeFollow; //設置地圖類型 _mapView.mapType=MKMapTypeStandard; //添加大頭針 [self addAnnotation]; }#pragma mark 添加大頭針 -(void)addAnnotation{ CLLocationCoordinate2D location1=CLLocationCoordinate2DMake(39.95, 116.35); KCAnnotation *annotation1=[[KCAnnotation alloc]init]; annotation1.title=@"CMJ Studio"; annotation1.subtitle=@"Kenshin Cui's Studios"; annotation1.coordinate=location1; annotation1.image=[UIImage imageNamed:@"icon_pin_floating.png"]; [_mapView addAnnotation:annotation1]; CLLocationCoordinate2D location2=CLLocationCoordinate2DMake(39.87, 116.35); KCAnnotation *annotation2=[[KCAnnotation alloc]init]; annotation2.title=@"Kenshin&Kaoru"; annotation2.subtitle=@"Kenshin Cui's Home"; annotation2.coordinate=location2; annotation2.image=[UIImage imageNamed:@"icon_paopao_waterdrop_streetscape.png"]; [_mapView addAnnotation:annotation2]; }#pragma mark - 地圖控件代理方法#pragma mark 顯示大頭針時調用,注意方法中的annotation參數是即將顯示的大頭針對象 -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{ //因爲當前位置的標註也是一個大頭針,因此此時須要判斷,此代理方法返回nil使用默認大頭針視圖 if ([annotation isKindOfClass:[KCAnnotation class]]) { static NSString *key1=@"AnnotationKey1"; MKAnnotationView *annotationView=[_mapView dequeueReusableAnnotationViewWithIdentifier:key1]; //若是緩存池中不存在則新建 if (!annotationView) { annotationView=[[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:key1]; annotationView.canShowCallout=true;//容許交互點擊 annotationView.calloutOffset=CGPointMake(0, 1);//定義詳情視圖偏移量 annotationView.leftCalloutAccessoryView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_classify_cafe.png"]];//定義詳情左側視圖 } //修改大頭針視圖 //從新設置此類大頭針視圖的大頭針模型(由於有多是從緩存池中取出來的,位置是放到緩存池時的位置) annotationView.annotation=annotation; annotationView.image=((KCAnnotation *)annotation).image;//設置大頭針視圖的圖片 return annotationView; }else { return nil; } } @end
運行效果:
注意:
在MapKit框架中除了MKAnnotationView以外還有一個MKPinAnnotationView,它是MKAnnotationView的子類,相比MKAnnotationView多了兩個屬性pinColor和animationDrop,分別用於設置大頭針視圖顏色和添加大頭針動畫。
經過上面的示例不難看出MKAnnotationView足夠強大(況且還有MKPinAnnotationView),不少信息均可以進行設置,可是惟獨不能修改大頭針描述詳情視圖(僅僅支持詳情中左右視圖內容)。要實現這個需求目前開發中廣泛採用的思路就是:
a.點擊一個大頭針A時從新在A的座標處添加另外一個大頭針B(注意此時將A對應的大頭針視圖canShowCallout設置爲false)做爲大頭針詳情模型,而後在- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation;代理方法中判斷大頭針類型,若是是B則重寫MKAnnotationView(能夠自定義一個類C繼承於MKAnnotationView),返回自定義大頭針視圖C。
b.定義大頭針視圖C繼承於MKAnnotationView(或者MKPinAnnotationView),在自定義大頭針視圖中添加本身的控件,完成自定義佈局。
在使用百度地圖客戶端時當點擊一個搜索位置時能夠看到此位置的評價等信息,視圖效果大概以下:
下面不妨試着實現一下這個效果:
大頭針模型:KCAnnotation.h
// // KCAnnotation.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import <Foundation/Foundation.h>#import <MapKit/MapKit.h>@interface KCAnnotation : NSObject<MKAnnotation> @property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *subtitle;#pragma mark 自定義一個圖片屬性在建立大頭針視圖時使用 @property (nonatomic,strong) UIImage *image;#pragma mark 大頭針詳情左側圖標 @property (nonatomic,strong) UIImage *icon;#pragma mark 大頭針詳情描述 @property (nonatomic,copy) NSString *detail;#pragma mark 大頭針右下方星級評價 @property (nonatomic,strong) UIImage *rate; @end
彈出詳情大頭針模型:KCCalloutAnnotation.h
// // KCCalloutAnnotation.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import <UIKit/UIKit.h>#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>@interface KCCalloutAnnotation : NSObject<MKAnnotation> @property (nonatomic) CLLocationCoordinate2D coordinate; @property (nonatomic, copy,readonly) NSString *title; @property (nonatomic, copy,readonly) NSString *subtitle;#pragma mark 左側圖標 @property (nonatomic,strong) UIImage *icon;#pragma mark 詳情描述 @property (nonatomic,copy) NSString *detail;#pragma mark 星級評價 @property (nonatomic,strong) UIImage *rate; @end
彈出詳情大頭針視圖:KCCalloutAnnotatonView.h
// // KCCalloutView.h // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // 自定義彈出標註視圖#import <UIKit/UIKit.h>#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>#import "KCCalloutAnnotation.h"@interface KCCalloutAnnotationView : MKAnnotationView @property (nonatomic ,strong) KCCalloutAnnotation *annotation;#pragma mark 從緩存取出標註視圖 +(instancetype)calloutViewWithMapView:(MKMapView *)mapView; @end
KCCalloutAnnotationView.m
// // KCCalloutView.m // MapKit // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import "KCCalloutAnnotationView.h"#define kSpacing 5#define kDetailFontSize 12#define kViewOffset 80 @interface KCCalloutAnnotationView(){ UIView *_backgroundView; UIImageView *_iconView; UILabel *_detailLabel; UIImageView *_rateView; } @end @implementation KCCalloutAnnotationView -(instancetype)init{ if(self=[super init]){ [self layoutUI]; } return self; } -(instancetype)initWithFrame:(CGRect)frame{ if (self=[super initWithFrame:frame]) { [self layoutUI]; } return self; } -(void)layoutUI{ //背景 _backgroundView=[[UIView alloc]init]; _backgroundView.backgroundColor=[UIColor whiteColor]; //左側添加圖標 _iconView=[[UIImageView alloc]init]; //上方詳情 _detailLabel=[[UILabel alloc]init]; _detailLabel.lineBreakMode=NSLineBreakByWordWrapping; //[_text sizeToFit]; _detailLabel.font=[UIFont systemFontOfSize:kDetailFontSize]; //下方星級 _rateView=[[UIImageView alloc]init]; [self addSubview:_backgroundView]; [self addSubview:_iconView]; [self addSubview:_detailLabel]; [self addSubview:_rateView]; } +(instancetype)calloutViewWithMapView:(MKMapView *)mapView{ static NSString *calloutKey=@"calloutKey1"; KCCalloutAnnotationView *calloutView=(KCCalloutAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:calloutKey]; if (!calloutView) { calloutView=[[KCCalloutAnnotationView alloc]init]; } return calloutView; }#pragma mark 當給大頭針視圖設置大頭針模型時能夠在此處根據模型設置視圖內容 -(void)setAnnotation:(KCCalloutAnnotation *)annotation{ [super setAnnotation:annotation]; //根據模型調整佈局 _iconView.image=annotation.icon; _iconView.frame=CGRectMake(kSpacing, kSpacing, annotation.icon.size.width, annotation.icon.size.height); _detailLabel.text=annotation.detail; float detailWidth=150.0; CGSize detailSize= [annotation.detail boundingRectWithSize:CGSizeMake(detailWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kDetailFontSize]} context:nil].size; float detailX=CGRectGetMaxX(_iconView.frame)+kSpacing; _detailLabel.frame=CGRectMake(detailX, kSpacing, detailSize.width, detailSize.height); _rateView.image=annotation.rate; _rateView.frame=CGRectMake(detailX, CGRectGetMaxY(_detailLabel.frame)+kSpacing, annotation.rate.size.width, annotation.rate.size.height); float backgroundWidth=CGRectGetMaxX(_detailLabel.frame)+kSpacing; float backgroundHeight=_iconView.frame.size.height+2*kSpacing; _backgroundView.frame=CGRectMake(0, 0, backgroundWidth, backgroundHeight); self.bounds=CGRectMake(0, 0, backgroundWidth, backgroundHeight+kViewOffset); } @end
主視圖控制器:KCMainViewController.m
// // KCMainViewController.m // MapKit Annotation // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // 37.785834 -122.406417 // 39.92 116.39#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>#import "KCAnnotation.h"#import "KCCalloutAnnotationView.h"#import "KCCalloutAnnotationView.h"@interface KCMainViewController ()<MKMapViewDelegate>{ CLLocationManager *_locationManager; MKMapView *_mapView; } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; [self initGUI]; }#pragma mark 添加地圖控件 -(void)initGUI{ CGRect rect=[UIScreen mainScreen].bounds; _mapView=[[MKMapView alloc]initWithFrame:rect]; [self.view addSubview:_mapView]; //設置代理 _mapView.delegate=self; //請求定位服務 _locationManager=[[CLLocationManager alloc]init]; if(![CLLocationManager locationServicesEnabled]||[CLLocationManager authorizationStatus]!=kCLAuthorizationStatusAuthorizedWhenInUse){ [_locationManager requestWhenInUseAuthorization]; } //用戶位置追蹤(用戶位置追蹤用於標記用戶當前位置,此時會調用定位服務) _mapView.userTrackingMode=MKUserTrackingModeFollow; //設置地圖類型 _mapView.mapType=MKMapTypeStandard; //添加大頭針 [self addAnnotation]; }#pragma mark 添加大頭針 -(void)addAnnotation{ CLLocationCoordinate2D location1=CLLocationCoordinate2DMake(39.95, 116.35); KCAnnotation *annotation1=[[KCAnnotation alloc]init]; annotation1.title=@"CMJ Studio"; annotation1.subtitle=@"Kenshin Cui's Studios"; annotation1.coordinate=location1; annotation1.image=[UIImage imageNamed:@"icon_pin_floating.png"]; annotation1.icon=[UIImage imageNamed:@"icon_mark1.png"]; annotation1.detail=@"CMJ Studio..."; annotation1.rate=[UIImage imageNamed:@"icon_Movie_Star_rating.png"]; [_mapView addAnnotation:annotation1]; CLLocationCoordinate2D location2=CLLocationCoordinate2DMake(39.87, 116.35); KCAnnotation *annotation2=[[KCAnnotation alloc]init]; annotation2.title=@"Kenshin&Kaoru"; annotation2.subtitle=@"Kenshin Cui's Home"; annotation2.coordinate=location2; annotation2.image=[UIImage imageNamed:@"icon_paopao_waterdrop_streetscape.png"]; annotation2.icon=[UIImage imageNamed:@"icon_mark2.png"]; annotation2.detail=@"Kenshin Cui..."; annotation2.rate=[UIImage imageNamed:@"icon_Movie_Star_rating.png"]; [_mapView addAnnotation:annotation2]; }#pragma mark - 地圖控件代理方法#pragma mark 顯示大頭針時調用,注意方法中的annotation參數是即將顯示的大頭針對象 -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{ //因爲當前位置的標註也是一個大頭針,因此此時須要判斷,此代理方法返回nil使用默認大頭針視圖 if ([annotation isKindOfClass:[KCAnnotation class]]) { static NSString *key1=@"AnnotationKey1"; MKAnnotationView *annotationView=[_mapView dequeueReusableAnnotationViewWithIdentifier:key1]; //若是緩存池中不存在則新建 if (!annotationView) { annotationView=[[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:key1];// annotationView.canShowCallout=true;//容許交互點擊 annotationView.calloutOffset=CGPointMake(0, 1);//定義詳情視圖偏移量 annotationView.leftCalloutAccessoryView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_classify_cafe.png"]];//定義詳情左側視圖 } //修改大頭針視圖 //從新設置此類大頭針視圖的大頭針模型(由於有多是從緩存池中取出來的,位置是放到緩存池時的位置) annotationView.annotation=annotation; annotationView.image=((KCAnnotation *)annotation).image;//設置大頭針視圖的圖片 return annotationView; }else if([annotation isKindOfClass:[KCCalloutAnnotation class]]){ //對於做爲彈出詳情視圖的自定義大頭針視圖無彈出交互功能(canShowCallout=false,這是默認值),在其中能夠自由添加其餘視圖(由於它自己繼承於UIView) KCCalloutAnnotationView *calloutView=[KCCalloutAnnotationView calloutViewWithMapView:mapView]; calloutView.annotation=annotation; return calloutView; } else { return nil; } }#pragma mark 選中大頭針時觸發//點擊通常的大頭針KCAnnotation時添加一個大頭針做爲所點大頭針的彈出詳情視圖-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{ KCAnnotation *annotation=view.annotation; if ([view.annotation isKindOfClass:[KCAnnotation class]]) { //點擊一個大頭針時移除其餘彈出詳情視圖 // [self removeCustomAnnotation]; //添加詳情大頭針,渲染此大頭針視圖時將此模型對象賦值給自定義大頭針視圖完成自動佈局 KCCalloutAnnotation *annotation1=[[KCCalloutAnnotation alloc]init]; annotation1.icon=annotation.icon; annotation1.detail=annotation.detail; annotation1.rate=annotation.rate; annotation1.coordinate=view.annotation.coordinate; [mapView addAnnotation:annotation1]; } }#pragma mark 取消選中時觸發 -(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{ [self removeCustomAnnotation]; }#pragma mark 移除所用自定義大頭針 -(void)removeCustomAnnotation{ [_mapView.annotations enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj isKindOfClass:[KCCalloutAnnotation class]]) { [_mapView removeAnnotation:obj]; } }]; } @end
在這個過程當中須要注意幾點:
1.大頭針A做爲一個普通大頭針,其中最好保存自定義大頭針視圖C所須要的模型以便根據不一樣的模型初始化視圖。
2.自定義大頭針視圖C的大頭針模型B中不須要title、subtitle屬性,最好設置爲只讀;模型中最後保存自定義大頭針視圖C所須要的佈局模型數據。
3.只有點擊非B類大頭針時才新增自定義大頭針,而且增長時要首先移除其餘B類大頭針避免重疊(通常建議放到取消大頭針選擇的代理方法中)。
4.一般在自定義大頭針視圖C設置大頭針模型時佈局界面,此時須要注意新增大頭針的位置,一般須要偏移必定的距離才能達到理想的效果。
運行效果:
除了可使用MapKit框架進行地圖開發,對地圖有精確的控制和自定義以外,若是對於應用沒有特殊要求的話選用蘋果自帶的地圖應用也是一個不錯的選擇。使用蘋果自帶的應用時須要用到MapKit中的MKMapItem類,這個類有一個openInMapsWithLaunchOptions:動態方法和一個openMapsWithItems: launchOptions:靜態方法用於打開蘋果地圖應用。第一個方法用於在地圖上標註一個位置,第二個方法除了能夠標註多個位置外還能夠進行多個位置之間的駕駛導航,使用起來也是至關方便。在熟悉這兩個方法使用以前有必要對兩個方法中的options參數作一下簡單說明:
鍵(常量) | 說明 | 值 |
MKLaunchOptionsDirectionsModeKey | 路線模式,常量 | MKLaunchOptionsDirectionsModeDriving 駕車模式 MKLaunchOptionsDirectionsModeWalking 步行模式 |
MKLaunchOptionsMapTypeKey | 地圖類型,枚舉 | MKMapTypeStandard :標準模式 MKMapTypeSatellite :衛星模式 MKMapTypeHybrid :混合模式 |
MKLaunchOptionsMapCenterKey | 中心點座標,CLLocationCoordinate2D類型 | |
MKLaunchOptionsMapSpanKey | 地圖顯示跨度,MKCoordinateSpan 類型 | |
MKLaunchOptionsShowsTrafficKey | 是否 顯示交通情況,布爾型 | |
MKLaunchOptionsCameraKey | 3D地圖效果,MKMapCamera類型 注意:此屬性從iOS7及之後可用,前面的屬性從iOS6開始可用 |
下面的代碼演示瞭如何在蘋果自帶地圖應用上標記一個位置,首先根據反地理編碼得到一個CLPlacemark位置對象,而後將其轉換爲MKPlacemark對象用於MKMapItem初始化,最後調用其openInMapsWithLaunchOptions:打開地圖應用並標記:
// // KCMainViewController.m // AppleMap // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>@interface KCMainViewController () @property (nonatomic,strong) CLGeocoder *geocoder; @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; _geocoder=[[CLGeocoder alloc]init]; [self location]; }#pragma mark 在地圖上定位 -(void)location{ //根據「北京市」進行地理編碼 [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark=[placemarks firstObject];//獲取第一個地標 MKPlacemark *mkplacemark=[[MKPlacemark alloc]initWithPlacemark:clPlacemark];//定位地標轉化爲地圖的地標 NSDictionary *options=@{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard)}; MKMapItem *mapItem=[[MKMapItem alloc]initWithPlacemark:mkplacemark]; [mapItem openInMapsWithLaunchOptions:options]; }]; } @end
運行效果:
若是要標記多個位置須要調用MKMapItem的靜態方法,下面的代碼演示中須要注意,使用CLGeocoder進行定位時一次只能定位到一個位置,因此第二個位置定位放到了第一個位置獲取成功以後。
// // KCMainViewController.m // AppleMap // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>@interface KCMainViewController () @property (nonatomic,strong) CLGeocoder *geocoder; @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; _geocoder=[[CLGeocoder alloc]init]; [self listPlacemark]; } -(void)listPlacemark{ //根據「北京市」進行地理編碼 [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark1=[placemarks firstObject];//獲取第一個地標 MKPlacemark *mkPlacemark1=[[MKPlacemark alloc]initWithPlacemark:clPlacemark1]; //注意地理編碼一次只能定位到一個位置,不能同時定位,所在放到第一個位置定位完成回調函數中再次定位 [_geocoder geocodeAddressString:@"鄭州市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark2=[placemarks firstObject];//獲取第一個地標 MKPlacemark *mkPlacemark2=[[MKPlacemark alloc]initWithPlacemark:clPlacemark2]; NSDictionary *options=@{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard)}; //MKMapItem *mapItem1=[MKMapItem mapItemForCurrentLocation];//當前位置 MKMapItem *mapItem1=[[MKMapItem alloc]initWithPlacemark:mkPlacemark1]; MKMapItem *mapItem2=[[MKMapItem alloc]initWithPlacemark:mkPlacemark2]; [MKMapItem openMapsWithItems:@[mapItem1,mapItem2] launchOptions:options]; }]; }]; } @end
運行效果:
要使用地圖導航功能在自帶地圖應用中至關簡單,只要設置參數配置導航模式便可,例如在上面代碼基礎上設置駕駛模式,則地圖應用會啓動駕駛模式計算兩點之間的距離同時對路線進行規劃。
// // KCMainViewController.m // AppleMap // // Created by Kenshin Cui on 14/3/27. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //#import "KCMainViewController.h"#import <CoreLocation/CoreLocation.h>#import <MapKit/MapKit.h>@interface KCMainViewController () @property (nonatomic,strong) CLGeocoder *geocoder; @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; _geocoder=[[CLGeocoder alloc]init]; [self turnByTurn]; } -(void)turnByTurn{ //根據「北京市」地理編碼 [_geocoder geocodeAddressString:@"北京市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark1=[placemarks firstObject];//獲取第一個地標 MKPlacemark *mkPlacemark1=[[MKPlacemark alloc]initWithPlacemark:clPlacemark1]; //注意地理編碼一次只能定位到一個位置,不能同時定位,所在放到第一個位置定位完成回調函數中再次定位 [_geocoder geocodeAddressString:@"鄭州市" completionHandler:^(NSArray *placemarks, NSError *error) { CLPlacemark *clPlacemark2=[placemarks firstObject];//獲取第一個地標 MKPlacemark *mkPlacemark2=[[MKPlacemark alloc]initWithPlacemark:clPlacemark2]; NSDictionary *options=@{MKLaunchOptionsMapTypeKey:@(MKMapTypeStandard),MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving}; //MKMapItem *mapItem1=[MKMapItem mapItemForCurrentLocation];//當前位置 MKMapItem *mapItem1=[[MKMapItem alloc]initWithPlacemark:mkPlacemark1]; MKMapItem *mapItem2=[[MKMapItem alloc]initWithPlacemark:mkPlacemark2]; [MKMapItem openMapsWithItems:@[mapItem1,mapItem2] launchOptions:options]; }]; }]; } @end
運行效果: