iOS封装一个简单的曲线图表视图
写在前面
前段时间做汇率的项目中,需要绘制汇率曲线,虽然知道关于图表相关的三方库,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,若有什么好的意见欢迎留言,如果觉得有帮助,感谢star,谢谢!