當(dāng)我們需要自定義某個室內(nèi)或者景點(diǎn)地圖的時候可以使用法挨。
實現(xiàn)效果基于NAMapKit第三方庫仲翎,這里暫時沒有添加定位的功能只是單純的導(dǎo)圖效果贰盗。
-
第一步
導(dǎo)入第三方庫 NAMapKit
如果你是直接下載拖入項目可能還需要單獨(dú)導(dǎo)入
SDWebImage
ARTiledImageView
這兩個第三方庫
-
第二步
實現(xiàn)NAMapKit自帶的別針氣泡效果
#import "NAPinAnnotationMapView.h"
#import "NAPinAnnotation.h"
- (void)viewDidLoad {
[super viewDidLoad];
/*注意
#import "NAMapView.h"
#import "NAAnnotation.h"
這兩個是最基礎(chǔ)的基類 第一個是最基本的顯示地圖(點(diǎn)擊別針沒有氣泡) 第二個是最基礎(chǔ)的地圖上的點(diǎn)
如果需要別針效果需要使用(NAPinAnnotation)如果需要點(diǎn)擊別針的氣泡效果需使用(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文件 里面是包含各個地圖上需要標(biāo)注的點(diǎn)的信息
//包含:點(diǎn)中心距離圖片頂端的距離(px)薛窥、點(diǎn)中心距離圖片左端的距離(px)味混、點(diǎn)標(biāo)題儒飒、點(diǎn)說明
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];
}
這樣 最簡單的帶別針帶點(diǎn)擊別針有氣泡效果的地圖就完成了
下面我們來看看基于NAMapKit基類自定義我們自己的地圖的思路
-
第三步 自定義
點(diǎn)
如前文所說 有別針效果的點(diǎn) 需要看NAPinAnnotation
這個類 所以我們仿照它來寫一個自己的點(diǎn)
- 它代表的只是地圖上的點(diǎn)(需要注意的是NAMapKit中 點(diǎn)和最終你在地圖上顯示出來的在點(diǎn)上的圖片(樣式)是分開的)
#import "NAAnnotation.h"
typedef enum {
WC,
Scene,
Location
} PinType;//點(diǎn)的類型 根據(jù)需要做修改 用于決定點(diǎn)顯示的圖片
//仿照"NAPinAnnotation"寫的點(diǎn)信息類 用于存儲你點(diǎn)的信息
@interface LJKPinAnnotation : NAAnnotation
// 點(diǎn)類型
@property (nonatomic, assign) PinType type;
// 點(diǎn)標(biāo)題
@property (nonatomic, copy) NSString *title;
// 點(diǎn)說明
@property (nonatomic, copy) NSString *subtitle;
// 根據(jù)點(diǎn)初始化
- (id)initWithPoint:(CGPoint)point;
@end
#import "LJKPinAnnotation.h"
#import "LJKPinAnnotationView.h"
#import "NAMapView.h"
@interface LJKPinAnnotation ()
@property (nonatomic, readonly, weak) LJKPinAnnotationView *view;//點(diǎn)上顯示的樣式稍后說明 注意一定要是weak否則會相互引用
@end
@implementation LJKPinAnnotation
@dynamic view;
- (id)initWithPoint:(CGPoint)point
{
self = [super initWithPoint:point];
if (self) {
self.type = WC;//默認(rèn)類型 根據(jù)需要寫
self.title = nil;
self.subtitle = nil;
}
return self;
}
- (UIView *)createViewOnMapView:(NAMapView *)mapView
{ //繼承自父類 創(chuàng)建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) {//動畫效果 根據(jù)需要改寫 這里是從中間往外 從無到有的擴(kuò)散動畫
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
點(diǎn)上顯示的樣式
這個看NAPinAnnotationView
類 我們仿照它來寫點(diǎn)上顯示的效果
#import <UIKit/UIKit.h>
#import "LJKPinAnnotation.h"
#import "LJKPinAnnotationMapView.h"
//仿照NAPinAnnotationView 寫的 在點(diǎn)上顯示的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;//減少會向下偏移
//實際運(yùn)用時根據(jù)自己的圖片大小和控件大小作調(diào)整
@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);
//這里我因為幾個圖片大小不太一樣所以為了看起來定位效果大致一樣所以加了個三目運(yùn)算符做調(diào)整
//實際運(yùn)用時根據(jù)自己的圖片大小和控件大小作調(diào)整
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;
//如果不是為了需要在某處調(diào)用這個方法可以不暴露在.h中
- (void)removeAllAnnotaions;
//如果需要頻繁切換顯示的點(diǎn) 可以自己添加一個清除現(xiàn)在地圖上顯示的點(diǎn)的方法
- (void)stopPlayAudio;
//如果不做音頻可以忽略
@end
#import "LJKPinAnnotationMapView.h"
#import "LJKPinAnnotationCallOutView.h"
#import "LJKPinAnnotation.h"
#import "LJKPinAnnotationView.h"
@interface LJKPinAnnotationMapView ()
@property (nonatomic, strong) LJKPinAnnotationCallOutView *calloutView;//點(diǎn)擊別針顯示的氣泡
@property (nonatomic, strong) UIView *calloutViewBackView;//因為我的氣泡是從XIB獲取的所以不加一個父視圖會出現(xiàn)尺寸異常 根據(jù)實際情況可以不要
@property (nonatomic, readonly) NSMutableArray *annotations;//因為添加了一個刪除點(diǎn)的方法所以需要自己管理點(diǎn) 不能再用父類中的數(shù)組
- (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
{
//重寫父類添加點(diǎn)的方法
[annotation addToMapView:self animated:animate];
[self.annotations addObject:annotation];
if ([annotation.view isKindOfClass:NSClassFromString(@"LJKPinAnnotationView")]) {
LJKPinAnnotation *annot = (LJKPinAnnotation *)annotation;
if (annot.type == Scene) {//我這里只有點(diǎn)擊景點(diǎn)別針才顯示氣泡 根據(jù)需求修改
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
{
//根據(jù)某個點(diǎn)的信息顯示氣泡
[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
// 在地圖上創(chuàng)建氣泡
- (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) {//根據(jù)是直接添加在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.創(chuàng)建 AVAudioPlayer 對象
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
// 3.打印歌曲信息
// NSString *msg = [NSString stringWithFormat:@"音頻文件聲道數(shù):%ld\n 音頻文件持續(xù)時間:%g",self.audioPlayer.numberOfChannels,self.audioPlayer.duration];
// NSLog(@"%@",msg);
// 4.設(shè)置循環(huán)播放次數(shù)
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}];
//點(diǎn)擊了詳情按鈕通知
}
@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];
}
- 注意
地圖上要顯示的點(diǎn)的坐標(biāo)應(yīng)該是 點(diǎn)中心到地圖左和上的px而不是pt 如果你想確認(rèn)點(diǎn)的位置是否正確可以
通過添加NADotAnnotation
到NAMapView
上來最直觀的查看 (此時在地圖上顯示的就是一個點(diǎn))
然后再根據(jù)圖片大小來調(diào)整你AnnotationView
的控件大小和偏移量
附上demo:Demo