iOS 自定義手繪地圖

當我們需要自定義某個室內或者景點地圖的時候可以使用。
實現效果基于NAMapKit第三方庫,這里暫時沒有添加定位的功能只是單純的導圖效果。

自定義地圖1.gif
自定義地圖2.gif

  • 第一步

導入第三方庫 NAMapKit
如果你是直接下載拖入項目可能還需要單獨導入
SDWebImage
ARTiledImageView 
這兩個第三方庫
  • 第二步

實現NAMapKit自帶的別針氣泡效果

#import "NAPinAnnotationMapView.h"
#import "NAPinAnnotation.h"

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /*注意
     #import "NAMapView.h"
     #import "NAAnnotation.h"
     這兩個是最基礎的基類 第一個是最基本的顯示地圖(點擊別針沒有氣泡) 第二個是最基礎的地圖上的點
     如果需要別針效果需要使用(NAPinAnnotation)如果需要點擊別針的氣泡效果需使用(NAPinAnnotationMapView)
     */
    
    NAPinAnnotationMapView *map = [[NAPinAnnotationMapView alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, self.view.bounds.size.height - 20)];
    map.backgroundColor  = [UIColor colorWithRed:214/255.0 green:214/255.0 blue:214/255.0 alpha:214/255.0];
    map.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    UIImage *image = [UIImage imageNamed:@"Map.png"];//你的地圖背景圖片
    map.minimumZoomScale = 0.1f;
    map.maximumZoomScale = 1.0f;
    [map displayMap:image];
    
    map.zoomScale = map.bounds.size.height / image.size.height;//地圖的初始縮放比例
    
    map.minimumZoomScale = map.minimumZoomScale < map.zoomScale ? map.zoomScale : map.minimumZoomScale;
    [self.view addSubview:map];
    
    NSArray *array = [ToolClass getJsonArray:@"scene_list.json"];
    //自己寫的本地Json文件  里面是包含各個地圖上需要標注的點的信息
    //包含:點中心距離圖片頂端的距離(px)、點中心距離圖片左端的距離(px)、點標題、點說明
    NSMutableArray *sceneDotArray = [NSMutableArray arrayWithCapacity:array.count];
    
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        NSDictionary *dic= obj;
        NAPinAnnotation *dot = [NAPinAnnotation annotationWithPoint:CGPointMake([dic[@"left"] doubleValue], [dic[@"top"] doubleValue])];
        dot.title = dic[@"title"];
        dot.subtitle = dic[@"subtitle"];
        dot.color = NAPinColorRed;
        [sceneDotArray addObject:dot];
        
    }];
    
    [map addAnnotations:sceneDotArray animated:YES];
}

這樣 最簡單的帶別針帶點擊別針有氣泡效果的地圖就完成了
下面我們來看看基于NAMapKit基類自定義我們自己的地圖的思路

  • 第三步 自定義

如前文所說 有別針效果的點 需要看NAPinAnnotation這個類 所以我們仿照它來寫一個自己的點

  • 它代表的只是地圖上的點(需要注意的是NAMapKit中 和最終你在地圖上顯示出來的在點上的圖片(樣式)是分開的)
#import "NAAnnotation.h"

typedef enum {
    WC,
    Scene,
    Location
} PinType;//點的類型 根據需要做修改 用于決定點顯示的圖片

//仿照"NAPinAnnotation"寫的點信息類 用于存儲你點的信息
@interface LJKPinAnnotation : NAAnnotation

// 點類型
@property (nonatomic, assign) PinType type;
// 點標題
@property (nonatomic, copy) NSString *title;
// 點說明
@property (nonatomic, copy) NSString *subtitle;

// 根據點初始化
- (id)initWithPoint:(CGPoint)point;

@end
#import "LJKPinAnnotation.h"
#import "LJKPinAnnotationView.h"
#import "NAMapView.h"

@interface LJKPinAnnotation ()
@property (nonatomic, readonly, weak) LJKPinAnnotationView *view;//點上顯示的樣式稍后說明 注意一定要是weak否則會相互引用
@end

@implementation LJKPinAnnotation
@dynamic view;

- (id)initWithPoint:(CGPoint)point
{
    self = [super initWithPoint:point];
    if (self) {
        self.type = WC;//默認類型 根據需要寫
        self.title = nil;
        self.subtitle = nil;
    }
    return self;
}

- (UIView *)createViewOnMapView:(NAMapView *)mapView
{   //繼承自父類 創建view顯示在地圖上的方法 必須重寫
    return [[LJKPinAnnotationView alloc] initWithAnnotation:self onMapView:mapView];
}

- (void)addToMapView:(NAMapView *)mapView animated:(BOOL)animate
{
    [super addToMapView:mapView animated:animate];
    
    [mapView addSubview:self.view];
    
    if (animate) {//動畫效果 根據需要改寫 這里是從中間往外 從無到有的擴散動畫
        self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.0f, 0.0f);
        
        __weak typeof(self) weakSelf = self;
        [UIView animateWithDuration:0.168 animations:^{
            weakSelf.view.transform = CGAffineTransformIdentity;
        }];
    }
}

- (void)updatePosition
{
    [self.view updatePosition];
}
@end
點上顯示的樣式

這個看NAPinAnnotationView 類 我們仿照它來寫點上顯示的效果

#import <UIKit/UIKit.h>
#import "LJKPinAnnotation.h"
#import "LJKPinAnnotationMapView.h"
//仿照NAPinAnnotationView 寫的 在點上顯示的view
@interface LJKPinAnnotationView : UIButton

@property (readwrite, nonatomic, weak)LJKPinAnnotation *annotation;

- (id)initWithAnnotation:(LJKPinAnnotation *)annotation onMapView:(NAMapView *)mapView;
//地圖縮放的時候更新位置
- (void)updatePosition;

@end
#import "LJKPinAnnotationView.h"

const CGFloat LJKMapViewAnnotationPinWidth = 55.0f;//自己的控件寬
const CGFloat LJKMapViewAnnotationPinHeight = 40.0f;//自己的控件高
const CGFloat LJKMapViewAnnotationPinPointX = 18.0f;//減少會向右偏移
const CGFloat LJKMapViewAnnotationPinPointY = 38.0f;//減少會向下偏移
//實際運用時根據自己的圖片大小和控件大小作調整
@interface LJKPinAnnotationView()
@property (nonatomic, weak) NAMapView *mapView;//注意weak
@end

@implementation LJKPinAnnotationView

- (id)initWithAnnotation:(LJKPinAnnotation *)annotation onMapView:(NAMapView *)mapView
{
    self = [super initWithFrame:CGRectZero];
    if (self) {
        self.mapView = mapView;
        self.annotation = annotation;
        
        NSString *pinImageName;
        switch (annotation.type) {
            case WC:
                pinImageName = @"wc";
                break;
            case Scene:
                pinImageName = @"guanguang";
                break;
            case Location:
                pinImageName = @"定位 (2)";
                break;
        }
        UIImage *pinImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:pinImageName ofType:@"png"]];
        [self setImage:pinImage forState:UIControlStateNormal];
        [[self imageView] setContentMode:UIViewContentModeScaleAspectFill];
        
    }
    return self;
}

- (void)updatePosition
{
    CGPoint point = [self.mapView zoomRelativePoint:self.annotation.point];
    point.x = point.x - (self.annotation.type == Location ? 28 : LJKMapViewAnnotationPinPointX);
    //這里我因為幾個圖片大小不太一樣所以為了看起來定位效果大致一樣所以加了個三目運算符做調整
    //實際運用時根據自己的圖片大小和控件大小作調整
    point.y = point.y - LJKMapViewAnnotationPinPointY;
    self.frame = CGRectMake(point.x, point.y, LJKMapViewAnnotationPinWidth, LJKMapViewAnnotationPinHeight);
}

@end
地圖

帶有氣泡效果的地圖看 NAPinAnnotationMapView 我們仿照它來寫帶有氣泡效果的地圖

#import "NAMapView.h"
@class LJKPinAnnotation;
@interface LJKPinAnnotationMapView : NAMapView

- (void)showCalloutForAnnotation:(LJKPinAnnotation *)annotation animated:(BOOL)animated;
//如果不是為了需要在某處調用這個方法可以不暴露在.h中
- (void)removeAllAnnotaions;
//如果需要頻繁切換顯示的點 可以自己添加一個清除現在地圖上顯示的點的方法
- (void)stopPlayAudio;
//如果不做音頻可以忽略
@end
#import "LJKPinAnnotationMapView.h"
#import "LJKPinAnnotationCallOutView.h"
#import "LJKPinAnnotation.h"
#import "LJKPinAnnotationView.h"

@interface LJKPinAnnotationMapView ()
@property (nonatomic, strong) LJKPinAnnotationCallOutView *calloutView;//點擊別針顯示的氣泡
@property (nonatomic, strong) UIView *calloutViewBackView;//因為我的氣泡是從XIB獲取的所以不加一個父視圖會出現尺寸異常 根據實際情況可以不要
@property (nonatomic, readonly) NSMutableArray *annotations;//因為添加了一個刪除點的方法所以需要自己管理點 不能再用父類中的數組
- (void)showCallOut:(id)sender;
- (void)hideCallOut;

@end

@implementation LJKPinAnnotationMapView

- (void)setupMap
{
    [super setupMap];
    
    _calloutView = [[LJKPinAnnotationCallOutView alloc] initOnMapView:self];
    _calloutViewBackView = [[UIView alloc] initWithFrame:_calloutView.frame];
    [_calloutViewBackView addSubview:_calloutView];
    _calloutView.userInteractionEnabled = YES;
    _calloutViewBackView.userInteractionEnabled = YES;
    [self addSubview:self.calloutViewBackView];
    
    _annotations = @[].mutableCopy;
}

- (void)addAnnotation:(NAAnnotation *)annotation animated:(BOOL)animate
{
    //重寫父類添加點的方法  
    [annotation addToMapView:self animated:animate];
    [self.annotations addObject:annotation];
    if ([annotation.view isKindOfClass:NSClassFromString(@"LJKPinAnnotationView")]) {
        LJKPinAnnotation *annot = (LJKPinAnnotation *)annotation;
        
        if (annot.type == Scene) {//我這里只有點擊景點別針才顯示氣泡 根據需求修改
            LJKPinAnnotationView *annotationView = (LJKPinAnnotationView *) annotation.view;
            [annotationView addTarget:self action:@selector(showCallOut:) forControlEvents:UIControlEventTouchDown];
        }
    }
    
    [self bringSubviewToFront:self.calloutViewBackView];
}

- (void)selectAnnotation:(NAAnnotation *)annotation animated:(BOOL)animate
{
    [self hideCallOut];
    if([annotation isKindOfClass:NSClassFromString(@"LJKAnnotation")]) {
        [self showCalloutForAnnotation:(LJKPinAnnotation *)annotation animated:animate];
    }
}

- (void)removeAnnotation:(NAAnnotation *)annotation
{
    [self hideCallOut];
    [annotation removeFromMapView];
    [self.annotations removeObject:annotation];
}

- (void)removeAllAnnotaions{
    
    [self hideCallOut];
    
    [self.annotations enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        LJKPinAnnotation *annot = obj;
        [annot removeFromMapView];
        [self.annotations removeObject:annot];
    }];
    
}

- (void)showCallOut:(id)sender
{
    if([sender isKindOfClass:[LJKPinAnnotationView class]]) {
        LJKPinAnnotationView *annontationView = (LJKPinAnnotationView *)sender;
        
        if ([self.mapViewDelegate respondsToSelector:@selector(mapView:tappedOnAnnotation:)]) {
            [self.mapViewDelegate mapView:self tappedOnAnnotation:annontationView.annotation];
        }//繼承自父類的代理
        
        [self showCalloutForAnnotation:annontationView.annotation animated:YES];
    }
}

- (void)showCalloutForAnnotation:(LJKPinAnnotation *)annotation animated:(BOOL)animated
{
    //根據某個點的信息顯示氣泡
    [self hideCallOut];
    
    self.calloutView.annotation = annotation;
    
    [self centerOnPoint:annotation.point animated:animated];
    
    CGFloat animationDuration = animated ? 0.1f : 0.0f;
    
    self.calloutView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.4f, 0.4f);
    self.calloutView.hidden = NO;
    
    __weak typeof(self) weakSelf = self;
    [UIView animateWithDuration:animationDuration animations:^{
        weakSelf.calloutView.transform = CGAffineTransformIdentity;
    }];
}

- (void)hideCallOut
{
    self.calloutView.hidden = YES;
    [self.calloutView stopPlay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.superview endEditing:YES];//如果地圖的父視圖上不會做輸入框可以不要
    
    UITouch *touch =  [touches anyObject];
    
    if (touch.view != self) {
        return;
    }
    
    if (!self.dragging) {
        [self hideCallOut];
    }
    
    [super touchesEnded:touches withEvent:event];
}

- (void)updatePositions
{
    [self.calloutView updatePosition];
    [super updatePositions];
}

- (void)stopPlayAudio{
    [self.calloutView stopPlay];
}

@end
氣泡

地圖上顯示的氣泡看NAPinAnnotationCallOutView 我們仿照它來寫氣泡效果

#import <UIKit/UIKit.h>
#import "LJKPinAnnotationMapView.h"
#import "LJKPinAnnotation.h"
@interface LJKPinAnnotationCallOutView : UIView

// 在地圖上創建氣泡
- (id)initOnMapView:(LJKPinAnnotationMapView *)mapView;

// 更新氣泡位置
- (void)updatePosition;

@property(readwrite, nonatomic, weak) LJKPinAnnotation *annotation;

- (void)stopPlay;

@end
#import "LJKPinAnnotationCallOutView.h"
#import <AVFoundation/AVFoundation.h>
@interface LJKPinAnnotationCallOutView ()
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UIImageView *sceneImageView;
@property (weak, nonatomic) IBOutlet UILabel *subtitleLabel;
@property (weak, nonatomic) IBOutlet UIButton *audioBtn;
@property (weak, nonatomic) IBOutlet UIButton *detailBtn;

@property (nonatomic, assign) CGPoint point;
@property (nonatomic, assign) CGPoint position;
@property (nonatomic, weak)   LJKPinAnnotationMapView *mapView;
@property (nonatomic, strong)AVAudioPlayer *audioPlayer;

@end

@implementation LJKPinAnnotationCallOutView


- (id)initOnMapView:(LJKPinAnnotationMapView *)mapView{
    
    self = [[NSBundle mainBundle] loadNibNamed:@"LJKPinAnnotationCallOutView" owner:nil options:nil].lastObject;
    if (self) {
        self.mapView = mapView;
        self.hidden  = YES;
    }
    return self;
}

- (void)setAnnotation:(LJKPinAnnotation *)annotation
{
    _annotation = annotation;
    self.position = annotation.point;
    self.point = annotation.point;

    [self updatePosition];
    
    self.titleLabel.text = annotation.title;
    self.subtitleLabel.text = annotation.subtitle;
    NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@", annotation.title] ofType:@".jpg"];
    self.sceneImageView.image = [UIImage imageWithContentsOfFile:path];
}

- (void)updatePosition
{
    CGPoint point = [self.mapView zoomRelativePoint:self.position];
    CGFloat xPos = point.x - (self.frame.size.width / 2.0f);
    CGFloat yPos = point.y - (self.frame.size.height) - 26;
    if (self.superview) {//根據是直接添加在MapView上還是多添加了一層view自己修改
        self.superview.frame = CGRectMake(floor(xPos), yPos, self.frame.size.width, self.frame.size.height);
    }else{
        self.frame = CGRectMake(floor(xPos), yPos, self.frame.size.width, self.frame.size.height);
    }
}

- (IBAction)playAudio:(UIButton *)sender {//如果不做播放音頻可以忽略
    
    if (sender.selected) {
        
        if (!self.audioPlayer) {
            // 1.獲取要播放音頻文件的URL
            NSURL *fileURL = [[NSBundle mainBundle]URLForResource:@"大美玉" withExtension:@".mp3"];
            // 2.創建 AVAudioPlayer 對象
            self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
            // 3.打印歌曲信息
            //    NSString *msg = [NSString stringWithFormat:@"音頻文件聲道數:%ld\n 音頻文件持續時間:%g",self.audioPlayer.numberOfChannels,self.audioPlayer.duration];
            //    NSLog(@"%@",msg);
            // 4.設置循環播放次數
            self.audioPlayer.numberOfLoops = -1;
        }
        // 5.開始播放
        [self.audioPlayer play];
    }else{
        [self.audioPlayer pause];
    }
    
}

- (void)stopPlay{
    if (self.audioPlayer) {
        [self.audioPlayer stop];
    }
}

- (IBAction)showDetail:(id)sender {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"ShowDetail" object:nil userInfo:@{@"annotation":self.annotation}];
    //點擊了詳情按鈕通知
}

@end
使用
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.navigationController setNavigationBarHidden:YES animated:YES];
    
    _mapView = [[LJKPinAnnotationMapView alloc] initWithFrame:CGRectMake(0, 20, self.view.bounds.size.width, self.view.bounds.size.height - 20 -49)];
    
    _mapView.backgroundColor  = [UIColor colorWithRed:214/255.0 green:214/255.0 blue:214/255.0 alpha:214/255.0];
    _mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    UIImage *image = [UIImage imageNamed:@"Map.png"];
    _mapView.minimumZoomScale = 0.1f;
    _mapView.maximumZoomScale = 1.0f;
    [_mapView displayMap:image];
   
    _mapView.zoomScale = (_mapView.bounds.size.height + 49)/ image.size.height;
    
    _mapView.minimumZoomScale = _mapView.minimumZoomScale < _mapView.zoomScale ? _mapView.zoomScale : _mapView.minimumZoomScale;
    
    [self.view addSubview:_mapView];
}

  • 注意
    地圖上要顯示的點的坐標應該是 點中心到地圖左和上px而不是pt 如果你想確認點的位置是否正確可以
    通過添加NADotAnnotationNAMapView上來最直觀的查看 (此時在地圖上顯示的就是一個點)

    然后再根據圖片大小來調整你AnnotationView的控件大小和偏移量

附上demo:Demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,200評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,076評論 25 708
  • 西安今天的天氣好惡心,重度霧霾不說了,關鍵因為快下雨了,外面濕氣特別重,于是,水加土就成了泥,能見度特別低,全世界...
    不吱閱讀 253評論 0 0
  • 快樂是什么?快樂一般有狹隘的快樂和廣義的快樂兩種。狹義的快樂追求欲望的滿足,廣義的快樂追求心靈的寧靜。 ...
    吳航斌閱讀 556評論 1 0
  • 馬上又要到了一年一度的高考了,考生們在如火如荼的復習中。 回想自己的高中生涯,只有一個感慨:你工作時流的汗就是你找...
    幕后產品汪閱讀 385評論 0 0