iOS 自定义手绘地图
2017-10-10 本文已影响953人
野生塔塔酱
当我们需要自定义某个室内或者景点地图的时候可以使用。
实现效果基于NAMapKit第三方库,这里暂时没有添加定位的功能只是单纯的导图效果。
-
第一步
导入第三方库 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 如果你想确认点的位置是否正确可以
通过添加NADotAnnotation
到NAMapView
上来最直观的查看 (此时在地图上显示的就是一个点)
然后再根据图片大小来调整你AnnotationView
的控件大小和偏移量
附上demo:Demo