iOS封装一个简单的曲线图表视图

写在前面

前段时间做汇率的项目中,需要绘制汇率曲线,虽然知道关于图表相关的三方库,github已经有很多大神级作品,但是我还是想自己尝试写一下,也是学习的过程嘛,所以我就自己按照项目需求做了个曲线图表视图,并进行了简单封装,效果如图:

图1:项目中的效果,模拟器对背后的网格显示有点问题,可以忽略

t1.gif

图2:简单的demo截图

t2.gif

功能

如上图,控件主要包含了几个功能点

1、绘制曲线

2、填充曲线围绕部分

3、背后网格线

4、左侧的行标和下方的列标显示

如何使用

github地址:封装一个简单的曲线图表视图XWCurveView,使用步骤如下:

1、导入XWCurveView.h头文件

2、初始化控件,设置pointValues属性,该属性为所有的绘制点的值的数组,每个绘制点用字典表示,字典必须包含key值为 XWCurveViewPointValuesRowValueKeyXWCurveViewPointValuesColumnValueKey 分别代表横纵的值,

3、配置其他可选的属性值

4、调用- (void)xw_drawCurveView;进行绘制或者重绘曲线视图

原理

绘制原理很简单,使用了CAShapeLayer + UIBezierPath,我们需要将pointValues中的所有值转换成控件中的坐标值,然后根据坐标值得到path即能得到曲线,转换的时候需要考虑到每个点的坐标和横纵最值的关系,最值可以手动设置,但如果没设置,可以通过pointValues计算得到最值,背后的网格我使用了CAReplicatorLayer,这是创建重复控件的利器,下面是主要的代码

/**
 *  整理传入的坐标值,按传入数据的值的横坐标值从小到大排序一下
 */
- (void)xwp_sortPointValues{
    NSMutableArray *temp = [NSMutableArray arrayWithArray:_pointValues];
    [temp sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
        if ([obj1[XWCurveViewPointValuesRowValueKey] floatValue] > [obj2[XWCurveViewPointValuesRowValueKey] floatValue]) {
            return NSOrderedDescending;
        }else{
            return NSOrderedAscending;
        }
    }];
    _pointValues = temp.copy;
}

/**
 *  计算横纵坐标的最值,如果没有设置就使用计算的最值
 */
- (void)xwp_checkEdgeValues{
    CGFloat rowMax = [_pointValues.lastObject[XWCurveViewPointValuesRowValueKey] floatValue];
    CGFloat rowMin = [_pointValues.firstObject[XWCurveViewPointValuesRowValueKey] floatValue];
    CGFloat columnMax = -MAXFLOAT;
    CGFloat columnMin = MAXFLOAT;
    for (NSDictionary *pointValue in _pointValues) {
        if ([pointValue[XWCurveViewPointValuesColumnValueKey] floatValue] > columnMax) {
            columnMax = [pointValue[XWCurveViewPointValuesColumnValueKey] floatValue];
        }
        if ([pointValue[XWCurveViewPointValuesColumnValueKey] floatValue] < columnMin) {
            columnMin = [pointValue[XWCurveViewPointValuesColumnValueKey] floatValue];
        }
    }
    if (!_rowMaxSettedFlag) {
        _rowMaxValue = rowMax;
    }
    if (!_rowMinSettedFlag) {
        _rowMinValue = rowMin;
    }
    if (!_columnMaxSettedFlag) {
        _columnMaxValue = columnMax;
    }
    if (!_columnMinSettedFlag) {
        _columnMinValue = columnMin;
    }
}


/**
 *  转换,将传入的点的值数组转换为坐标数组
 */
- (void)xwp_changePointArrayFromValueArray{
    _pointArray = @[].mutableCopy;
    for (NSDictionary *dict in _pointValues) {
        CGPoint point = [self xwp_changePointFromValue:dict];
        [_pointArray addObject:[NSValue valueWithCGPoint:point]];
    }
}

/**
 *  将传入的点根据值转换为坐标
 */
- (CGPoint)xwp_changePointFromValue:(NSDictionary *)dict{
    CGFloat rowValue = [dict[XWCurveViewPointValuesRowValueKey] floatValue];
    CGFloat columnValue = [dict[XWCurveViewPointValuesColumnValueKey] floatValue];
    CGPoint point = CGPointMake(_mainContainer.width / (_rowMaxValue - _rowMinValue) * (rowValue - _rowMinValue), _mainContainer.height / (_columnMaxValue - _columnMinValue) * (_columnMaxValue - columnValue));
    return point;
}

/**
 *  根据转换的坐标点构建绘制曲线的path和填充曲线的path
 */

- (void)xwp_makePath{
    UIBezierPath * path = [UIBezierPath bezierPath];
    UIBezierPath *backPath = [UIBezierPath bezierPath];
    CGPoint firstPoint = [_pointArray[0] CGPointValue];
    CGPoint lastPoint = [_pointArray[_pointArray.count - 1] CGPointValue];
    [path moveToPoint:firstPoint];
    [backPath moveToPoint:CGPointMake(firstPoint.x, _mainContainer.height)];
    for (NSValue *pointValue in _pointArray) {
        CGPoint point = [pointValue CGPointValue];
        if (pointValue == _pointArray[0]) {
            [backPath addLineToPoint:point];
            continue;
        }
        [backPath addLineToPoint:point];
        [path addLineToPoint:point];
    }
    [backPath addLineToPoint:CGPointMake(lastPoint.x, _mainContainer.height)];
    _path = path;
    _backPath = backPath;
}

/**
 *  根据path绘制曲线
 */

- (void)xwp_drawCurveWithPath{
    _backLayer.path = _backPath.CGPath;
    _curveLineLayer.path = _path.CGPath;
    _curveLineLayer.strokeEnd = 1;
    if (_drawWithAnimation) {
        CABasicAnimation *pointAnim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        pointAnim.fromValue = @0;
        pointAnim.toValue = @1;
        pointAnim.duration = _drawAnimationDuration;
        [_curveLineLayer addAnimation:pointAnim forKey:@"drawLine"];
    }
}

/**
 *  上面除了绘制的操作,关于计算点和path的操作最后都应该在异步线程进行,如果点过多,会造成主线程阻塞
 */
- (void)xwp_setCurveLine{
    if (!_pointValues.count) {
        NSLog(@"pointValues为空,没有可绘制的点");
        return;
    }
    dispatch_async(dispatch_queue_create("处理计算点和path的队列", NULL), ^{
        [self xwp_sortPointValues];
        [self xwp_checkEdgeValues];
        [self xwp_changePointArrayFromValueArray];
        [self xwp_makePath];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self xwp_drawCurveWithPath];
        });
    });
}

4、如上就是主要的曲线绘制代码了,逻辑是非常简单的,其它细节代码请查看源代码

最后

控件比较简单,我仅仅是对自己的思路做了个总结,自己实现过一次毕竟印象要深刻许多,要是自己以后还要用到这类功能有能更快的集成了,github地址:封装一个简单的曲线图表视图XWCurveView,若有什么好的意见欢迎留言,如果觉得有帮助,感谢star,谢谢!

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,279评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,688评论 22 664
  • 这张图的来源是Matt的博客 2017对数据极客来说还算不错.在去年, 我们提了一个问题: 大数据还是个事么? L...
    chesser阅读 316评论 0 0
  • 1. scathing No matter how they are scathing to you, we su...
    Mr_Oldman阅读 290评论 0 0
  • 踏~踏~踏~一个缓慢略带轻浮的脚步声从远处的巷口传来,一盏老久满是锈迹的路灯,残烛般昏黄的灯光忽明忽暗,没有给这陈...
    黑羽雨秋阅读 229评论 0 1