iOS-加載SVG類型的圖片並且實現可點擊長按功能

首先介紹下該項目實現了那些功能

  • svg圖片數據解析

  • 繪製svg圖片

  • svg圖片按照區域設置不同顏色


  • 點擊svg圖片不同區域能高亮顯示該點擊區域



    點擊美國區域,美國變成紅色,切換到加拿大,美國恢復原有顏色,加拿大高亮紅色


  • 長按svg圖片彈框顯示該區域的信息


  • 點擊中國區域跳轉至中國的svg圖片 長按陝西區域彈框


看到了上面的效果,是不是很想知道怎麼實現的呢,彆着急下面會給出實現代碼,並且還有完整Demo。

看下整個工程的結構:



從圖片可以看出並沒有多少代碼,source中的都是一些三方的文件。

  1. 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章