专注iOS开发常用组件iOS动画

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

2016-04-06  本文已影响3908人  wazrx

写在前面

前段时间做汇率的项目中,需要绘制汇率曲线,虽然知道关于图表相关的三方库,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,谢谢!

上一篇下一篇

猜你喜欢

热点阅读