iOS-加载SVG类型的图片并且实现可点击长按功能

首先介绍下该项目实现了那些功能

  • svg图片数据解析

  • 绘制svg图片

  • svg图片按照区域设置不同颜色


    绘制图片.png
  • 点击svg图片不同区域能高亮显示该点击区域


    点击高亮.png

    点击美国区域,美国变成红色,切换到加拿大,美国恢复原有颜色,加拿大高亮红色


    切换高亮.png
  • 长按svg图片弹框显示该区域的信息


    长按弹框.png
  • 点击中国区域跳转至中国的svg图片 长按陕西区域弹框


    长按高亮.png

看到了上面的效果,是不是很想知道怎么实现的呢,别着急下面会给出实现代码,并且还有完整Demo。

看下整个工程的结构:


项目结构.png

从图片可以看出并没有多少代码,source中的都是一些三方的文件。

  1. svg图片数据解析

先看下svg格式的数据 左边是浏览器显示的图片,中间是数据格式。


svg数据格式.png

那代码中是如何解析这样的数据的呢?
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342