iOS 自定义折线图

2019-08-01  本文已影响0人  小阿辉Huiz

iOS 自定义折线图

效果及 Demo

由于产品需求要展示分数,并且60分以下展示需要压缩比例,而开源的图表库太过复杂并且有些无法满足需求,学习成本偏高,于是决定自己写一个,效果如下图。

Demo地址

chart-w480

视图层级及设计思路

  1. 使用UICollectionView实现滚动效果,并且部分展示元素可以复用,如下图中日期label、虚线及圆点
  2. 使用CAShapeLayer+UIBezierPath实现折线及阴影绘制
cell-w480

代码实现

图表实现代码

#import "ZHLineChartView.h"

#import "ZHLineChartConst.h"
#import "ZHLineChartModel.h"

@interface ZHLineChartView ()

@end


@implementation ZHLineChartView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // 这步是因为,有其他 layer 时,折线图需要展示在最底层
        self.layer.zPosition = -1.f;
    }
    return self;
}

#pragma mark - Load Subviews

- (void)makeDraw {
    // x轴的 y
    CGFloat moveStartY = kChartViewH;
    
    UIBezierPath *aPath = [UIBezierPath bezierPath];//填充镀层
    UIBezierPath *aPathLine = [UIBezierPath bezierPath];//线
    
    // 所有经过的坐标点,画线
//    NSInteger startI = 0; // 用于计算无效点阴影
    BOOL ignoreZeroPoint = YES;
    for (int i = 0; i < _dataModels.count; i++) {
        CGFloat yPoint = _dataModels[i].yPoint;
        if (ignoreZeroPoint && yPoint == kInvaildYPoint) { //忽略前面为0的点(产品需求)
            continue;
        }
        CGFloat xPoint = kLineChartMoveStartX + i * kLineChartMoveW;
        if (ignoreZeroPoint) {
            //设置开始的坐标点
            //取得第一个有效点的 index
//            startI = i;
            [aPath moveToPoint:CGPointMake(xPoint, moveStartY)]; //镀层要从 x 轴开始画,再添加第一个点坐标
            [aPath addLineToPoint:CGPointMake(xPoint, yPoint)];
            [aPathLine moveToPoint:CGPointMake(xPoint, yPoint)];
            ignoreZeroPoint = NO; //开始画点,不再忽略为0的点
        } else {
            [aPath addLineToPoint:CGPointMake(xPoint, yPoint)];
            [aPathLine addLineToPoint:CGPointMake(xPoint, yPoint)];
        }
    }
    
    //镀层收边
    CGFloat lineW = kLineChartMoveStartX + (_dataModels.count - 1) * kLineChartMoveW;
    [aPath addLineToPoint:CGPointMake(lineW, moveStartY)];
    [aPath closePath];
    
    // 前面为 0 的点灰色蒙层,UI 后来又不要了
//    CAGradientLayer *grayLayer = [CAGradientLayer layer];
//    grayLayer.colors = @[(__bridge id)[[UIColor lightGrayColor] colorWithAlphaComponent:0.f].CGColor, (__bridge id)[[UIColor lightGrayColor] colorWithAlphaComponent:0.4f].CGColor];
//    grayLayer.frame = CGRectMake(kLineChartMoveStartX, 0.f, startI * kLineChartMoveW, moveStartY);
//    grayLayer.startPoint = CGPointZero;
//    grayLayer.endPoint = CGPointMake(1.f, 1.f);
//    [self.layer addSublayer:grayLayer];
    
    
    [self addGradientLayerWithPath:aPath];
    [self addLineLayerWithPath:aPathLine];
    [self addXLineLayer];
}

- (void)addXLineLayer {
    CGFloat lineW = kLineChartMoveStartX + (_dataModels.count - 1) * kLineChartMoveW;
    //x 轴
    UIBezierPath *xLinePath = [UIBezierPath bezierPath];
    [xLinePath moveToPoint:CGPointMake(kLineChartMoveStartX - kLineChartMoveW / 2.f, kChartViewH)];
    [xLinePath addLineToPoint:CGPointMake(kLineChartMoveStartX + lineW, kChartViewH)];
    
    CAShapeLayer *xLineLayer = [CAShapeLayer layer];
    xLineLayer.strokeColor = [UIColor lightGrayColor].CGColor;
    xLineLayer.lineWidth = 0.5f;
    xLineLayer.path = xLinePath.CGPath;
    xLineLayer.zPosition = -2;
    [self.layer addSublayer:xLineLayer];
}

- (void)addLineLayerWithPath:(UIBezierPath *)path {
    //折线
    CAShapeLayer *shapelayerLine = [CAShapeLayer layer];
    //设置边框颜色,就是上边画的,线的颜色
    shapelayerLine.strokeColor = [[UIColor orangeColor] CGColor];
    //设置填充颜色 如果不需要[UIColor clearColor]
    shapelayerLine.fillColor = [[UIColor clearColor] CGColor];
    //就是这句话在关联彼此(UIBezierPath和CAShapeLayer):
    shapelayerLine.path = path.CGPath;
    [self.layer addSublayer:shapelayerLine];
}

- (void)addGradientLayerWithPath:(UIBezierPath *)path {
    //获取总共的长度
    CGFloat lineW = kLineChartMoveStartX + (_dataModels.count - 1) * kLineChartMoveW;
    
    CAShapeLayer *shapelayer = [CAShapeLayer layer];
    //设置边框颜色,就是上边画的,线的颜色
    shapelayer.strokeColor = [[UIColor orangeColor] CGColor];
    //设置填充颜色
    shapelayer.fillColor = [[UIColor orangeColor] CGColor];
    //就是这句话在关联彼此(UIBezierPath和CAShapeLayer):
    shapelayer.path = path.CGPath;
    
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.colors = @[(__bridge id)[[UIColor orangeColor] colorWithAlphaComponent:0.4f].CGColor, (__bridge id)[[UIColor orangeColor] colorWithAlphaComponent:0.f].CGColor];
    //    gradientLayer.locations = @[@0.f, @0.5f];
    gradientLayer.startPoint = CGPointMake(0.5, 0.f);
    gradientLayer.endPoint = CGPointMake(0.5, 1.f);
    gradientLayer.shadowPath = path.CGPath;
    gradientLayer.frame = CGRectMake(0, 0, lineW, kChartViewH);
    gradientLayer.mask = shapelayer;
    [self.layer addSublayer:gradientLayer];
}

#pragma mark - Setter

- (void)setDataModels:(NSArray<ZHLineChartModel *> *)dataModels {
    _dataModels = dataModels;
    
    [self makeDraw];
}


@end
#import "ZHLineChartModel.h"

#import "ZHLineChartConst.h"

@interface ZHLineChartModel ()

@property (nonatomic, strong, readwrite) NSString *date;
@property (nonatomic, strong, readwrite) NSString *score;
@property (nonatomic, assign, readwrite) CGFloat yPoint;

@end

@implementation ZHLineChartModel

#pragma mark - Helper

- (void)setupYPoint {
    //这步放在 model 中是因为方便模型转换时异步计算 y 坐标
    CGFloat score = [self.score intValue];
    CGFloat point = kItemTopMargin + kItemHeight / 2.f;
    
    /**
     高度计算,100-60分,按比例计算,小于 60 分时,小于 60 的区域按比例计算
     */
    CGFloat height = 4 * (kItemHeight + kItemSpace); // 60 - 100 的总高度
    if (score >= 60.f && score < 100) {
        height = (100.f - score) / 40.f * height; // 按比例下降高度
        point += height;
    } else if (score < 60) {
        // 小于 60 分,最后一段按比例计算 Y 值
        CGFloat tempH = kItemHeight / 2.f + kItemSpace;
        tempH = (60.f - score) / 60.f * tempH;
        point += tempH + height;
    }
    
    self.yPoint = point;
}


#pragma mark - Test

+ (NSArray <ZHLineChartModel *>*)testDataArray {
    NSArray *dateArr = @[@"08-29",@"08-30",@"08-31",@"09-01",@"09-02",@"09-03",@"09-04",@"09-05",@"09-06",@"09-07",@"09-08",@"09-09"];
    NSArray *scoreArr = @[@"20",@"60",@"40",@"55",@"100",@"80",@"90",@"70",@"77",@"30",@"80",@"88"];
    
    NSMutableArray *arr = [NSMutableArray array];
    
    for (NSInteger i = 0; i < dateArr.count; i++) {
        ZHLineChartModel *model = [[ZHLineChartModel alloc] init];
        model.score = scoreArr[i];
        model.date = dateArr[i];
        [model setupYPoint];
        
        [arr addObject:model];
    }
    
    return arr.copy;
}

@end

其他代码详见 Demo

上一篇下一篇

猜你喜欢

热点阅读