iOS學習筆記20-地圖(二)MapKit框架

一、地圖開發介紹

從iOS6.0開始地圖數據不再由谷歌驅動,而是改用自家地圖,當然在國內它的數據是由高德地圖提供的。

在iOS中進行地圖開發主要有三種方式:
  • 利用MapKit框架進行地圖開發,利用這種方式可以對地圖進行精準的控制
  • 調用蘋果官方自帶的地圖應用,主要用于一些簡單的地圖應用,無法精確控制
  • 使用第三方地圖開發SDK庫

用得最多的還是MapKit,所以這節就只講MapKit的使用。

二、MapKit核心類

MapKit的核心類為地圖展示控件MKMapView,以下是常用的屬性、對象方法以及代理方法。

1. 屬性:

/* 用戶位置跟蹤 */
@property (nonatomic) BOOL showsUserLocation;/*< 是否在地圖上標注用戶位置 */
@property (nonatomic, readonly) MKUserLocation *userLocation;/*< 用戶位置 */
@property (nonatomic) MKUserTrackingMode userTrackingMode;/*< 用戶跟蹤類型 */
typedef NS_ENUM(NSInteger, MKUserTrackingMode) {
    MKUserTrackingModeNone = 0, /*< 不跟蹤 */
    MKUserTrackingModeFollow, /*< 跟蹤 */
    MKUserTrackingModeFollowWithHeading,  /*< 導航跟蹤 */
};
/* 設置地圖配置項 */
@property (nonatomic) MKMapType mapType;/*< 地圖類型 */
@property (nonatomic, readonly) NSArray *annotations;/*< 大頭針數組 */
typedef NS_ENUM(NSUInteger, MKMapType) {
    MKMapTypeStandard = 0,/*< 標準地圖 */
    MKMapTypeSatellite,/*< 衛星地圖 */
    MKMapTypeHybrid,/*< 混合模式(標準+衛星) */
    MKMapTypeSatelliteFlyover,/*< 3D立體衛星(iOS9.0) */
    MKMapTypeHybridFlyover,/*< 3D立體混合(iOS9.0) */
}
/* 設置地圖控制項 */
@property (nonatomic) BOOL zoomEnabled;/*< 是否可以縮放 */
@property (nonatomic) BOOL scrollEnabled;/*< 是否可以滾動 */
@property (nonatomic) BOOL rotateEnabled;/*< 是否可以旋轉 */
@property (nonatomic) BOOL pitchEnabled;/*< 是否顯示3D視角 */
/* 設置地圖顯示項 */
@property (nonatomic) BOOL showsBuildings;/*< 是否顯示建筑物,只影響標準地圖 */
@property (nonatomic) BOOL showsTraffic;/*< 是否顯示交通,iOS9 */
@property (nonatomic) BOOL showsCompass;/*< 是否顯示指南針,iOS9 */
@property (nonatomic) BOOL showsScale;/*< 是否顯示比例尺,iOS9 */

所謂大頭針就是地圖上顯示的這個標注:


大頭針圖片

2. 對象方法:

/* 添加大頭針 */
- (void)addAnnotation:(id <MKAnnotation>)annotation;
- (void)addAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
/* 刪除大頭針 */
- (void)removeAnnotation:(id <MKAnnotation>)annotation;
- (void)removeAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
/* 選中大頭針與取消選中大頭針 */
- (void)selectAnnotation:(id <MKAnnotation>)annotation 
                animated:(BOOL)animated;
- (void)deselectAnnotation:(id <MKAnnotation>)annotation 
                  animated:(BOOL)animated;
/* 獲取大頭針視圖 */
- (MKAnnotationView *)viewForAnnotation:(id <MKAnnotation>)annotation;
/* 從緩沖池中取出大頭針視圖控件 */
- (MKAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier;
/* 設置顯示區域以及地圖中心坐標 */
- (void)setRegion:(MKCoordinateRegion)region 
         animated:(BOOL)animated;
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate 
                   animated:(BOOL)animated;
/* 經緯度坐標轉UIKit坐標,UIKit坐標轉經緯度坐標 */
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate 
               toPointToView:(UIView *)view;
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point 
                  toCoordinateFromView:(UIView *)view;

3. 常用代理方法MKMapViewDelegate

/* 地圖加載完成會調用 */
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView;
/* 地圖加載失敗會調用 */
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error;
/* 用戶位置發生改變會調用 */
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation;
/* 顯示區域改變會調用 */
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
/* 點擊選中大頭針時會調用 */
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view;
/* 取消選中大頭針時會調用 */
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view;
/* 顯示地圖上的大頭針,功能類似于UITableView的tableView:cellForRowAtIndexPath:方法 */
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation;

三、MapKit使用

1. 首先添加頭文件:

#import <MapKit/MapKit.h>

2. 初始化地圖展示控件MKMapView

- (void)initMapView{
    CGFloat x = 0;
    CGFloat y = 20;
    CGFloat width = self.view.frame.size.width;
    CGFloat height = self.view.frame.size.height;
    //創建MKMapView,設置控件視圖大小
    MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(x, y, width, height)];
    //設置地圖類型
    mapView.mapType = MKMapTypeStandard;
    //設置代理
    mapView.delegate = self;
    [self.view addSubview:mapView];
    self.mapView = mapView;
}

3. 用戶位置跟蹤

在iOS8之前,實現這個功能只需要:
  1. 設置用戶跟蹤模式
  1. mapView:DidUpdateUserLocation:代理方法中設置地圖中心和顯示范圍
在iOS8之后,用法稍有不同:
  1. 必須按照前面的定位章節的,獲取前臺或者前后臺的定位服務授權,下面是鏈接:
    iOS學習筆記19-地圖(一)定位CoreLocation
  1. 不需要進行中心點的指定,默認會將當前位置設置為中心點并自動顯示區域范圍
  2. 只有定位到當前位置后mapView:DidUpdateUserLocation:代理方法才會調用
- (void)viewDidLoad {
    [super viewDidLoad];
    //獲取定位服務授權
    [self requestUserLocationAuthor];
    //初始化MKMapView
    [self initMapView];
}
- (void)requestUserLocationAuthor{
    //如果沒有獲得定位授權,獲取定位授權請求
    self.locationM = [[CLLocationManager alloc] init];
    if ([CLLocationManager locationServicesEnabled]) {
        if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse) {
            [self.locationM requestWhenInUseAuthorization];
        }
    }
}
- (void)initMapView{
    CGFloat x = 0;
    CGFloat y = 20;
    CGFloat width = self.view.frame.size.width;
    CGFloat height = self.view.frame.size.height;
    //創建MKMapView對象
    MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(x, y, width, height)];
    //設置地圖類型
    mapView.mapType = MKMapTypeStandard;
    //設置用戶跟蹤模式
    mapView.userTrackingMode = MKUserTrackingModeFollow;
    mapView.delegate = self;
    [self.view addSubview:mapView];
    self.mapView = mapView;
}
#pragma mark - MKMapViewDelegate
/* 更新用戶位置會調用 */
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
    CLLocation *location = userLocation.location;
    CLLocationCoordinate2D coordinate = location.coordinate;
    NSLog(@"經度:%f,緯度:%f",coordinate.latitude,coordinate.longitude);
}
用戶位置跟蹤

4. 添加大頭針

MapKit沒有自帶的大頭針,只有大頭針協議MKAnnotation,我們需要自定義大頭針:

  1. 創建一個繼承NSObject的類
  1. 實現MKAnnotation協議
  2. 必須創建一個屬性,用于存儲大頭針位置
@property (nonatomic) CLLocationCoordinate2D coordinate;
下面就是我簡單創建的LTAnnotation類:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface LTAnnotation : NSObject <MKAnnotation>
/* 必須創建的屬性 */
@property (nonatomic) CLLocationCoordinate2D coordinate;
/* 可選的屬性 */
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
/* 自定義的屬性 */
@property (nonatomic, strong) UIImage *icon;
@end

@implementation LTAnnotation
@end
下面是實際的使用:
- (void)viewDidLoad {
    [super viewDidLoad];
    //請求定位授權
    [self requestUserLocationAuthor];
    //初始化MKMapView
    [self initMapView];
    //添加大頭針
    [self addAnnotationsToMapView];
}
- (void)addAnnotationsToMapView{
    CLLocationCoordinate2D location1 = CLLocationCoordinate2DMake(22.54, 114.02);
    //創建大頭針
    LTAnnotation *annotation = [[LTAnnotation alloc] init];
    annotation.title = @"執著";
    annotation.subtitle = @"執著哥開的店";
    annotation.coordinate = location1;
    annotation.icon = [UIImage imageNamed:@"red"];
    //添加大頭針
    [self.mapView addAnnotation:annotation1];
}
大頭針在地圖上的顯示
點擊大頭針顯示

5. 自定義大頭針視圖

上面的大頭針樣子是不是很丑,那是MKMapView的默認樣式大頭針視圖MKAnnotationView,我們先來了解下它的常用屬性:

@property (nonatomic, strong) id<MKAnnotation> annotation;/*< 大頭針數據 */
@property (nonatomic, strong) UIImage *image;/*< 大頭針的圖標 */
@property (nonatomic, readonly) NSString *reuseIdentifier;/*< 大頭針的唯一標示 */
@property (nonatomic) CGPoint calloutOffset;/*< 彈出視圖的偏移 */
@property (nonatomic) BOOL selected;/*< 是否選中 */
@property (nonatomic) BOOL canShowCallout;/*< 是否能點擊彈出視圖 */
@property (nonatomic, strong) UIView *leftCalloutAccessoryView;/*< 彈出視圖左邊的視圖 */
@property (nonatomic, strong) UIView *rightCalloutAccessoryView;/*< 彈出視圖右邊的視圖 */
下面是通過設置MKAnnotationView的屬性,自定義大頭針視圖:
/* 每當大頭針顯示在可視界面上時,就會調用該方法,用戶位置的藍色點也是個大頭針,也會調用 */
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
    if ([annotation isKindOfClass:[LTAnnotation class]]) {
        LTAnnotation *annotationLT = (LTAnnotation *)annotation;
        //類似于UITableViewCell的重用機制,大頭針視圖也有重用機制
        static NSString *key = @"AnnotationIdentifier";
        MKAnnotationView *view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:key];
        if (!view) {
            view = [[MKAnnotationView alloc] initWithAnnotation:annotation
                                                reuseIdentifier:key];
        }
        //設置大頭針數據
        view.annotation = annotation;
        //自定義大頭針默認是NO,表示不能彈出視圖,這里讓大頭針可以點擊彈出視圖
        view.canShowCallout = YES;
        //設置大頭針圖標
        view.image = annotationLT.icon;
        //設置彈出視圖的左邊視圖
        UIImage *leftImage = [UIImage imageNamed:@"cafeIcon"];
        UIImageView *leftView = [[UIImageView alloc] initWithImage: leftImage];
        leftView.bounds = CGRectMake(0, 0, 50, 50);
        view.leftCalloutAccessoryView = leftView;
        //設置彈出視圖的右邊視圖
        UIImage *rightImage = [UIImage imageNamed:@"cafeRight"];
        UIImageView *rightView = [[UIImageView alloc] initWithImage: rightImage];
        rightView.bounds = CGRectMake(0, 0, 50, 50);
        view.rightCalloutAccessoryView = rightView;
        return view;
    }
    //返回nil,表示顯示默認樣式
    return nil;
}
改變默認樣式的大頭針視圖

四、擴展--自定義大頭針彈出詳情視圖

如果你去關注下一些地圖應用,會發現他們的彈出視圖和我們的完全不一樣,那是怎么實現的呢?

實際上那不是彈出視圖,那是個大頭針,只是這個大頭針做得和彈出視圖很像而已。

實現思路:
  1. 當點擊普通的大頭針時,移除地圖上其他的詳情大頭針,添加當前大頭針的詳情大頭針
  2. 當普通大頭針取消選中時,移除地圖上所有的詳情大頭針
  3. mapView:viewForAnnotation:方法中設置普通大頭針視圖和詳情大頭針視圖
下面是實現的部分代碼【實現效果比較隨便,見諒】:
#pragma mark - 地圖控件代理方法
/* 顯示大頭針時調用,注意方法中的annotation參數是即將顯示的大頭針對象 */
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
    //由于當前位置的標注也是一個大頭針,所以此時需要判斷,此代理方法返回nil使用默認大頭針視圖
    if ([annotation isKindOfClass:[LTAnnotation class]]) {
        static NSString *key1 = @"AnnotationKey1";
        MKAnnotationView *annotationView = [_mapView dequeueReusableAnnotationViewWithIdentifier:key1];
        //如果緩存池中不存在則新建
        if (!annotationView) {
            annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
                                                          reuseIdentifier:key1];
            annotationView.canShowCallout = NO;//不允許彈出視圖,但可以被選中
        }
        //重新設置此類大頭針視圖的大頭針模型(因為有可能是從緩存池中取出來的,位置是放到緩存池時的位置)
        annotationView.annotation = annotation;
        annotationView.image = ((LTAnnotation *)annotation).icon;//設置大頭針視圖的圖片
        return annotationView;
    }else if([annotation isKindOfClass:[LTCalloutAnnotation class]]){
        static NSString *key2 = @"AnnotationCallOutKey2";
        MKAnnotationView *calloutView = [_mapView dequeueReusableAnnotationViewWithIdentifier:key2];
        //如果緩存池中不存在則新建
        if (!calloutView) {
            calloutView = [[MKAnnotationView alloc] initWithAnnotation:annotation
                                                       reuseIdentifier:key2];
            calloutView.canShowCallout = NO;//不允許彈出視圖,但可以被選中
        }
        //對于作為彈出詳情視圖的自定義大頭針視圖無彈出交互功能,在其中可以自由添加其他視圖
        calloutView.annotation = annotation;
        //設置詳情大頭針的偏移位置
        calloutView.centerOffset = CGPointMake(-50, -80);
        [self calloutAddSubView:calloutView];
        return calloutView;
    } else {
        return nil;
    }
}

上面我的LTCalloutAnnotation和LTAnnotation實際上是只是類名不同而已,屬性都一樣。

#pragma mark 添加彈出視圖的子控件,這里我就很隨便了,你可以搞得好看點
- (void)calloutAddSubView:(MKAnnotationView *)calloutView
{
    //添加背景
    UIView *background = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 60)];
    background.backgroundColor = [UIColor whiteColor];
    background.layer.borderWidth = 5;
    background.layer.borderColor = [UIColor blueColor].CGColor;
    [calloutView addSubview:background];
    //添加圖片
    UIImage *image = [UIImage imageNamed:@"cafeRight"];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    imageView.frame = CGRectMake(5, 5, 50, 50);
    [calloutView addSubview:imageView];
    //添加一個紅色方塊
    UIView *subview = [[UIView alloc] initWithFrame:CGRectMake(60, 5, 35, 40)];
    subview.backgroundColor = [UIColor redColor];
    [calloutView addSubview:subview];
}

#pragma mark 選中大頭針時觸發
//點擊一般的大頭針KCAnnotation時添加一個大頭針作為所點大頭針的彈出詳情視圖
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
    if ([view.annotation isKindOfClass:[LTAnnotation class]]) {
        LTAnnotation *annotation = view.annotation;
        //點擊一個大頭針時移除其他彈出詳情視圖
        [self removeCalloutAnnotation];
        //添加詳情大頭針
        LTCalloutAnnotation *callout = [[LTCalloutAnnotation alloc] init];
        callout.icon = annotation.icon;
        callout.title = annotation.title;
        callout.subtitle = annotation.subtitle;
        callout.coordinate = annotation.coordinate;
        [self.mapView addAnnotation:callout];
    }
}
#pragma mark 取消選中時觸發
-(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{
    [self removeCalloutAnnotation];
}
#pragma mark 移除所用詳情大頭針
-(void)removeCalloutAnnotation{
    [self.mapView.annotations enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop){
        if ([obj isKindOfClass:[LTCalloutAnnotation class]]) {
            [_mapView removeAnnotation:obj];
        }
    }];
}
自定義彈出視圖的效果圖

這個自定義彈出詳情視圖,我做的比較簡陋,我主要是為了好說明具體是怎么實現的,你可以把彈出界面做的好看點,順便把一些大頭針視圖進行下封裝,那一切就很完美了,O(∩_∩)O哈!這種實現是很低效的,每次都需要遍歷所有的大頭針,從中找到詳情大頭針,需要優化的地方很多,可以自己去想著優化。

如果有什么意見請在下方評論區寫出來,求關注,求打賞,O(∩_∩)O哈!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容