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

執著丶執念發表於2018-06-02

###一、地圖開發介紹 從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哈!

相關文章