首先介绍下该项目实现了那些功能
svg图片数据解析
绘制svg图片
-
svg图片按照区域设置不同颜色
-
点击svg图片不同区域能高亮显示该点击区域
点击美国区域,美国变成红色,切换到加拿大,美国恢复原有颜色,加拿大高亮红色
-
长按svg图片弹框显示该区域的信息
-
点击中国区域跳转至中国的svg图片 长按陕西区域弹框
看到了上面的效果,是不是很想知道怎么实现的呢,别着急下面会给出实现代码,并且还有完整Demo。
看下整个工程的结构:
从图片可以看出并没有多少代码,source中的都是一些三方的文件。
- svg图片数据解析
先看下svg格式的数据 左边是浏览器显示的图片,中间是数据格式。
那代码中是如何解析这样的数据的呢?
ViewController.m中的代码:
- (void)viewDidLoad {
[super viewDidLoad];
//创建地图
CHMapModel *mapModel = [CHMapModel mapWithName:@"world.svg"];
_mapView = [CHMapView mapWithMapModel:mapModel];
_mapView.mapDelegate = self;
_mapView.frame = self.view.frame;
[self.view addSubview:_mapView];
//添加按钮
_backBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.width-60, 40, 50, 25)];
[_backBtn setTitle:@"Back" forState:UIControlStateNormal];
_backBtn.titleLabel.font = [UIFont systemFontOfSize:13];
_backBtn.hidden = YES;
[_backBtn addTarget:self action:@selector(back:) forControlEvents:UIControlEventTouchUpInside];
_backBtn.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
[self.view addSubview:_backBtn];
[self.view bringSubviewToFront:_backBtn];
}
根据CHMapModel *mapModel = [CHMapModel mapWithName:@"world.svg"];
对解析svg数据一定在CHMapModel
中,调到CHMapModel
中。
#import "CHMapModel.h"
#import "XMLDictionary.h"
@implementation CHMapModel
+ (instancetype)mapWithName:(NSString*)name{
return [[CHMapModel alloc] initWithName:name];
}
- (instancetype)initWithName:(NSString*)name{
self = [super init];
if (self) {
self.name = name;
//解析数据
[self parserData:name];
}
return self;
}
- (void)parserData:(NSString*)name{
NSString *svgPath = [[NSBundle mainBundle] pathForResource:name ofType:nil];
NSData *svgData = [NSData dataWithContentsOfFile:svgPath];
XMLDictionaryParser *parser=[[XMLDictionaryParser alloc]init];
NSDictionary *dic=[parser dictionaryWithData:svgData];
self.height = [dic[@"_height"] floatValue];
self.width = [dic[@"_width"] floatValue];
NSDictionary *dict = dic[@"g"];
NSArray *pathArr = dict[@"path"];
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict2 in pathArr) {
CHRegion *region = [CHRegion regionWithDictionary:dict2];
[array addObject:region];
}
self.regionArray = [array copy];
}
@end
这里使用了一个三方库:XMLDictionary 可以在gitHub上搜到
那解析到的数据是什么样的呢?请看下面
{
"_d" = "M499.8,374.1 L498.9,371.5 L498.4,367.8 L496.3,365.4 L497.1,364.6 L494.9,361.1 L496.0,358.5 L498.2,356.6 L496.2,354.6 L493.5,353.3 L492.0,353.9 L489.8,350.5 L490.0,349.1 L491.7,345.8 L493.4,345.1 L496.3,344.9 L496.6,341.5 L495.8,334.1 L493.6,334.7 L492.1,336.0 L489.6,333.7 L487.5,333.3 L487.3,329.3 L484.0,327.9 L483.6,326.1 L484.6,325.5 L486.7,326.0 L488.5,325.1 L489.2,323.6 L489.1,319.9 L492.7,318.5 L492.0,316.2 L492.6,314.9 L491.8,314.0 L492.9,311.8 L496.7,312.4 L496.8,313.8 L499.5,316.6 L502.4,315.4 L505.1,313.0 L503.8,310.6 L503.9,308.1 L502.0,308.3 L499.0,305.8 L498.9,304.0 L500.3,303.9 L501.7,302.9 L506.9,306.4 L508.7,306.5 L509.8,309.9 L511.4,310.9 L515.7,311.4 L517.1,312.0 L518.7,313.8 L518.5,316.1 L523.4,315.4 L524.1,316.5 L523.4,319.6 L522.7,319.5 L521.2,323.3 L523.1,324.5 L524.8,324.0 L525.1,327.8 L525.9,329.9 L527.2,330.5 L531.2,330.4 L532.8,326.8 L534.5,326.9 L536.6,329.0 L537.2,332.2 L535.8,334.5 L533.4,332.3 L529.2,332.8 L530.7,334.5 L530.5,337.7 L528.8,338.3 L527.3,340.5 L526.9,342.0 L528.6,343.3 L528.5,344.3 L531.2,345.5 L531.0,346.2 L533.4,346.5 L533.7,348.1 L532.2,350.5 L533.0,352.4 L536.3,352.1 L537.4,351.4 L539.7,351.3 L539.9,352.7 L543.3,353.4 L542.8,357.6 L541.2,360.7 L540.4,360.1 L538.7,362.1 L539.7,362.5 L540.7,364.7 L538.9,365.7 L534.3,365.5 L534.0,368.0 L534.6,370.5 L532.7,372.8 L532.5,374.2 L530.4,375.8 L528.2,378.4 L525.7,379.1 L524.9,377.5 L523.2,376.8 L519.7,377.1 L517.4,376.2 L515.8,373.2 L513.5,373.0 L513.0,371.9 L511.5,373.2 L511.7,375.0 L509.3,377.3 L507.2,377.4 L506.5,375.2 L508.1,374.4 L509.2,372.0 L507.9,370.6 L506.1,370.2 L504.2,372.6 L500.9,374.2 Z";
"_fill" = "#CEE3F5";
"_id" = "CN.AH";
"_stroke" = "#6E6E6E";
"_stroke-width" = "0.4";
desc = {
"_xmlns" = "http://www.highcharts.com/svg/namespace";
"alt-name" = "?nhu?";
country = China;
fips = CH01;
hasc = "CN.AH";
"hc-a2" = AH;
"hc-key" = "cn-ah";
"hc-middle-x" = "0.44";
"hc-middle-y" = "0.59";
labelrank = 2;
latitude = "31.9537";
longitude = "117.253";
name = Anhui;
"postal-code" = AH;
region = "East China";
subregion = Central;
type = "Sh?ng";
"type-en" = Province;
"woe-id" = 12578022;
"woe-label" = "Anhui, CN, China";
"woe-name" = Anhui;
};
},
这些数据最主要用来绘制图片的是"_d"中存的坐标点,然而这些数据并不能直接使用,如何处理这些数据各位大神肯定有不同的处理办法,这里就不赘述,代码中有处理过程。
- 绘制svg图片,并按照区域设置不同颜色
拿到了数据就要去绘制工作了,从上面ViewController.m
中的代码可以看到
CHMapView
根据解析到的数据创建了地图图片,设置了代理,并且添加到view
上,那接下来就去看下CHMapView.m
中是如何绘制的。
+ (instancetype)mapWithMapModel:(CHMapModel*)model{
return [[CHMapView alloc] initWithMapModel:model];
}
- (instancetype)initWithMapModel:(CHMapModel*)model{
self = [super init];
if (self) {
self.backgroundColor = [UIColor colorWithRed:109/255.0 green:224/255.0 blue:244/255.0 alpha:1.0];
self.layers = [NSMutableArray array];
self.bounces = NO;
self.delegate = self;
_mapView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, model.width, model.height)];
[self addSubview:_mapView];
_mapView.backgroundColor = [UIColor colorWithRed:109/255.0 green:224/255.0 blue:244/255.0 alpha:1.0];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[_mapView addGestureRecognizer:tap];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
[_mapView addGestureRecognizer:longPress];
self.contentSize = CGSizeMake(model.width, model.height);
[self setMapData:model];
}
return self;
}
- (void)setMapData:(CHMapModel*)model{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSArray *regionArray = model.regionArray;
for (CHRegion*region in regionArray) {
UIColor *regionColor = [self randomColor];
[dict setObject:regionColor forKey:region.name];
for (NSArray *pointArray in region.pathArray) {
CHPolygonLayer *layer = [CHPolygonLayer layerWithPoints:pointArray];
layer.name = region.name;
UIColor *color = [dict objectForKey:region.name];
layer.fillColor = color.CGColor;
[self.layer addSublayer:layer];
[self.layers addObject:layer];
}
}
}
在- (void)setMapData:(CHMapModel*)model{}
方法中,实现了绘制和给不同区域着色。细心的童鞋应该已经看到了CHPolygonLayer
,没错就是它,最终的绘制工作是交给了CHPolygonLayer
。
//
#import "CHPolygonLayer.h"
#import "CHPoint.h"
@implementation CHPolygonLayer
+ (instancetype)layerWithPoints:(NSArray<CHPoint*>*)points{
return [[CHPolygonLayer alloc] initWithPoints:points];
}
- (instancetype)initWithPoints:(NSArray<CHPoint*>*)points{
self = [super init];
if (self) {
self.count = points.count;
self.pointArray = points;
[self drawPathWith:points];
}
return self;
}
- (void)drawPathWith:(NSArray*)points{
//创建path
UIBezierPath *path = [UIBezierPath bezierPath];
for (int i = 0; i<points.count; i++) {
CHPoint *point = points[i];
if (i == 0) {
[path moveToPoint:point.point];
}else{
[path addLineToPoint:point.point];
}
}
[path closePath];
self.path = path.CGPath;
self.lineWidth = 1;
self.strokeColor = [UIColor grayColor].CGColor;
}
@end
到这里就完成了绘制和不同区域的着色工作
- 点击svg图片不同区域能高亮显示该点击区域
从创建CHMapView
的代码中可以看到在初始化过程中添加了点击手势,如何选中并让其高亮呢?
/**
* 点击事件
*/
- (void)tap:(UITapGestureRecognizer*)gesture{
CGPoint point = [gesture locationInView:_mapView];
// 1.先还原之前点击的国家或省份
for (CHPolygonLayer *layer in self.selectedLayerArray) {
layer.fillColor = self.selectedlayerColor.CGColor;
}
// 2.获取到当前点击的国家或省份的名称和颜色
for (CHPolygonLayer *layerp in self.layers) {
BOOL isIn = [self isInPolygon:layerp point:point];
if (isIn) {
self.selectedLayerName = layerp.name;
self.selectedlayerColor = [UIColor colorWithCGColor:layerp.fillColor];
//点击事件
if ([self.mapDelegate respondsToSelector:@selector(chmapView:didClickRegion:)]) {
[self.mapDelegate chmapView:self didClickRegion:layerp.name];
}
break;
}
}
// 3.保存点击中的某个国家或某个省的所有地区
[self.selectedLayerArray removeAllObjects];
for (CHPolygonLayer *layer in self.layers) {
if ([self.selectedLayerName isEqualToString:layer.name]) {
layer.fillColor = [UIColor redColor].CGColor;
[self.selectedLayerArray addObject:layer];
}
}
}
- 长按svg图片弹框显示该区域的信息
从创建CHMapView
的代码中可以看到在初始化过程中添加了长安手势,如何选中并让其弹框呢?
/**
* 长按事件
*/
- (void)longPress:(UITapGestureRecognizer*)gesture{
if (gesture.state == UIGestureRecognizerStateBegan) {
CGPoint pointV = [gesture locationInView:[UIApplication sharedApplication].keyWindow];
CGPoint point = [gesture locationInView:_mapView];
for (CHPolygonLayer *layerp in self.layers) {
BOOL isIn = [self isInPolygon:layerp point:point];
if (isIn) {
[self showWithoutImage:pointV name:layerp.name];
break;
}
}
}
}
/**
* 弹框
*/
- (void)showWithoutImage:(CGPoint)point name:(NSString*)name{
PopoverView *popoverView = [PopoverView popoverView];
popoverView.style = PopoverViewStyleDark;
popoverView.hideAfterTouchOutside = YES; // 点击外部时不允许隐藏
// 不带图片
PopoverAction *action = [PopoverAction actionWithTitle:name handler:^(PopoverAction *action) {
}];
PopoverAction *action1 = [PopoverAction actionWithTitle:@"面积:8888万平方公里" handler:^(PopoverAction *action) {
}];
PopoverAction *action2 = [PopoverAction actionWithTitle:@"人口:8亿" handler:^(PopoverAction *action) {
}];
PopoverAction *action3 = [PopoverAction actionWithTitle:@"GDP:88888万亿" handler:^(PopoverAction *action) {
}];
[popoverView showToPoint:point withActions:@[action,action1, action2, action3]];
}
对了如何从世界地图中选中点中的那个国家的呢?这里用到了一个算法。关于算法我就不在这里介绍了,网上的资料一大堆,童鞋们自行研究。
/**
* 检查某点是否包含在多边形的范围内(需要优化) 规定点在边上或顶点上属于多边形的内部
*/
- (BOOL)isInPolygon:(CHPolygonLayer*)polygon point:(CGPoint)point {
NSUInteger verticesCount = polygon.count;
NSArray *pointArray = polygon.pointArray;
int nCross = 0;
for (int i = 0; i < verticesCount; ++ i) {
CHPoint *pointx = pointArray[i];
CHPoint *pointm = pointArray[(i + 1) % verticesCount];
float j = pointx.point.x;
float k = pointx.point.y;
float m = pointm.point.x;
float n = pointm.point.y;
CGPoint p1 = CGPointMake(j, k);
CGPoint p2 = CGPointMake(m, n);
//点在多边形的顶点上
// if (point.y == p1.y && point.x == p1.x) {
// return NO;
// }
//求解 y=point.y与 p1 p2 的交点
if ( p1.y == p2.y ) { // p1p2与 y=p0.y平行
continue;
}
if ( point.y < fminf(p1.y, p2.y) ) {//交点在p1p2延长线上 (规定)
continue;
}
if ( point.y > fmaxf(p1.y, p2.y) ) {//交点在p1p2延长线上
continue;
}
//求交点的 X坐标
double x = (double)(point.y - p1.y) * (double)(p2.x - p1.x) / (double)(p2.y - p1.y) + p1.x;
// double y = point.y;
if ( x > point.x ) { // 只统计单边交点
nCross++;
}
//当射线穿过多边形的顶点的时候 规定交点属于射线上侧(也就是说只算一次穿越)
// if (x == p1.x && y == p1.y && p2.y<y) {
// nCross--;
// }
}
if(nCross%2 != 0) { //单边交点为偶数,点在多边形之外
return YES;
} else {
return NO;
}
}
至此就全部讲完了,具体的代码童鞋们可以下载代码了解在这里献上完整代码地址:https://github.com/caihuaGao/LoadSVGImage.git