美文网首页
iOS 自定义折线图

iOS 自定义折线图

作者: 小阿辉Huiz | 来源:发表于2019-08-01 10:57 被阅读0次

    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

    相关文章

      网友评论

          本文标题:iOS 自定义折线图

          本文链接:https://www.haomeiwen.com/subject/chhllctx.html