iOS 自定义折线图
效果及 Demo
chart-w480由于产品需求要展示分数,并且60分以下展示需要压缩比例,而开源的图表库太过复杂并且有些无法满足需求,学习成本偏高,于是决定自己写一个,效果如下图。
视图层级及设计思路
- 使用
UICollectionView
实现滚动效果,并且部分展示元素可以复用,如下图中日期label
、虚线及圆点 - 使用
CAShapeLayer
+UIBezierPath
实现折线及阴影绘制
代码实现
图表实现代码
#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
网友评论