写在前面
前段时间做汇率的项目中,需要绘制汇率曲线,虽然知道关于图表相关的三方库,github已经有很多大神级作品,但是我还是想自己尝试写一下,也是学习的过程嘛,所以我就自己按照项目需求做了个曲线图表视图,并进行了简单封装,效果如图:
图1:项目中的效果,模拟器对背后的网格显示有点问题,可以忽略
t1.gif
图2:简单的demo截图
t2.gif
功能
如上图,控件主要包含了几个功能点
1、绘制曲线
2、填充曲线围绕部分
3、背后网格线
4、左侧的行标和下方的列标显示
如何使用
github地址:封装一个简单的曲线图表视图XWCurveView,使用步骤如下:
1、导入XWCurveView.h
头文件
2、初始化控件,设置pointValues属性,该属性为所有的绘制点的值的数组,每个绘制点用字典表示,字典必须包含key值为 XWCurveViewPointValuesRowValueKey
和 XWCurveViewPointValuesColumnValueKey
分别代表横纵的值,
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
作者:wazrx
链接:https://www.jianshu.com/p/24459aa51330