首先介紹下該項目實現了那些功能
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