iOS 一个UIView搞定图表

效果图

在.h中只有一个方法,传入X轴、Y轴刻度值数组及数据数组即可方便的绘制图表。

@interface ZKChartView : UIView

/**
 * 绘制图表
 *
 * @param xLabels X轴刻度值
 * @param yLabels Y轴刻度值
 * @param values  数据
 *
 */
- (void)reloadWithXLabels:(NSArray *)xLabels
                  yLabels:(NSArray *)yLabels
                   values:(NSArray *)values;

@end

在UIViewController中调用

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 图表
    ZKChartView *chartView=[[ZKChartView alloc]initWithFrame:CGRectMake(0, 20, 375, 234)];
    chartView.backgroundColor = [UIColor colorWithRed:39/255.0 green:52/255.0 blue:68/255.0 alpha:1.0];
    
    // 图表标题
    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, 375, 44)];
    titleLabel.textAlignment = NSTextAlignmentCenter;
    titleLabel.textColor = [UIColor whiteColor];
    titleLabel.font = [UIFont systemFontOfSize:16.0];
    titleLabel.text = @"2018年12年第3周营业额";
    
    [self.view addSubview:chartView];
    [self.view addSubview:titleLabel];
    
    // 图表数据
    [chartView reloadWithXLabels:@[ @"12/16", @"12/17", @"12/18", @"12/19", @"12/20", @"12/22", @"12/23" ]
                         yLabels:@[ @"50000.00", @"40000.00", @"30000.00", @"20000.00", @"10000.00"]
                          values:@[ @"36000.00", @"28000.00", @"40000.00", @"42000.00", @"39000.00", @"44000.00", @"32000.00" ]];
}

@end
前言

APP开发中经常有一些绘制图表的需求,本文采用在UIView中通过addSublayer:方法添加各种CALayer来实现基本的绘图,用到以下CALayer

  • CAGradientLayer 绘制图表渐变色
  • CAShapeLayer 绘制线条
  • CATextLayer 绘制文字
  • CALayer 绘制圆点

以上CALayer的用法都比较简单,本文主要叙述绘制图表的基本思路,满足一般图表的绘制需求。

基本思路
  1. 确定在UIView中绘图的区域(红色区域),构建坐标系。
  2. 绘制X轴、Y轴刻度线,确定对应的刻度文字显示区域。
  3. 绘制各个点值对应的线,更新X轴Y轴刻度文字。
  4. 绘制图表渐变色。
  5. 给UIView添加手势,点击图表时显示对应的值。
代码实现
  1. 确定在UIView中绘图的区域,构建坐标系。
    图表左侧为Y轴刻度值,底部为X轴刻度线及刻度值,顶部为了显示图表标题,因此在代码中用CGRect contentRect变量来设定图表据上下左右的距离,绘制图表时以此来确定各个值对应的位置。
- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        
        // 图表区域
        UIEdgeInsets contentInsets = UIEdgeInsetsMake(48, 53, 34, 20);
        self.contentRect = CGRectMake(contentInsets.left,
                                      contentInsets.top,
                                      CGRectGetWidth(frame) - contentInsets.left - contentInsets.right,
                                      CGRectGetHeight(frame) - contentInsets.top - contentInsets.bottom);
        
        // 初始化X轴坐标
        [self initXLines];
        [self initXLabels];
        
        // 初始化Y轴坐标
        [self initYLines];
        [self initYLabels];
    }
    return self;
}
  1. 绘制X轴、Y轴刻度线,确定对应的刻度文字显示区域。
    图表区域在Y轴方向上分为4等份,X轴上分为6等份。所以需要在图表区域画5条水平线,X轴上画7条垂直线。
  • 绘制Y轴水平线,根据contentRect来计算5条线的起始坐标,用UIBezierPath绘制路径,CAShapeLayer画线,然后把5条线添加到CALayer上,再添加到UIView上。
// Y轴等分线
- (void)initYLines {
    
    NSUInteger lineCount = 5;   // 等分线数量
    UIColor *lineColor = [UIColor colorWithRed:124/255.0 green:133/255.0 blue:138/255.0 alpha:1.0]; // 等分线颜色
    CGFloat lineWidth = 0.5;    // 等分线宽度
    
    CGFloat lineSpace = CGRectGetHeight(self.contentRect) / (lineCount - 1);
    self.yPoints = [NSMutableArray arrayWithCapacity:lineCount];
    
    CGFloat startX = CGRectGetMinX(self.contentRect);
    CGFloat endX = CGRectGetMaxX(self.contentRect);
    CGFloat startY = CGRectGetMinY(self.contentRect);
    
    CALayer *yLayer = [CALayer layer];
    for (NSInteger i = 0; i < lineCount; i++) {
        
        CGPoint startPoint = CGPointMake(startX, startY + lineSpace * i);
        CGPoint endPoint = CGPointMake(endX, startPoint.y);
        
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:startPoint];
        [path addLineToPoint:endPoint];
        
        CAShapeLayer *lineLayer = [CAShapeLayer layer];
        lineLayer.fillColor = [UIColor clearColor].CGColor;
        lineLayer.strokeColor = lineColor.CGColor;
        lineLayer.lineWidth = lineWidth;
        lineLayer.path = path.CGPath;
        
        [yLayer addSublayer:lineLayer];
        
        // 记录
        [self.yPoints addObject:[NSValue valueWithCGPoint:startPoint]];
    }
    
    [self.layer addSublayer:yLayer];
}
  • 绘制Y轴刻度线对应的刻度值,CATextLayer可以设定背景色、文字的颜色和字体等,用来绘制文字。刻度值文字的中心和水平线对齐,第1步中左侧预留的空间来放置刻度值文字。
// Y轴刻度值
- (void)initYLabels {
    
    UIFont *font = [UIFont systemFontOfSize:8 weight:UIFontWeightLight]; // 文字字体
    self.yLayers = [NSMutableArray arrayWithCapacity:self.yPoints.count];
    
    for (NSUInteger i = 0; i < self.yPoints.count; i++) {
        
        NSValue *value = self.yPoints[i];
        CGPoint point = [value CGPointValue];
        
        CATextLayer *textLayer = [self textLayerWithFont:font];
        textLayer.frame = CGRectMake(0, point.y - 5, 49, 10); // frame设定是关键
        
        [self.layer addSublayer:textLayer];
        [self.yLayers addObject:textLayer];
    }
}
  • 绘制X轴刻度线、刻度值同理
  1. 绘制各个点值对应的线,更新X轴Y轴刻度文字。
  2. 绘制图表渐变色。
    绘制点值线,主要是计算数据值对应的坐标点,用UIBezierPath绘制路径。用CAGradientLayer来绘制渐变色,数据对应的点值线增加起始点来作为CAGradientLayer的mask,这样就会在绘图区域形成渐变色,其他区域不显示渐变色。

/**
 * 绘制图表
 *
 * @param xLabels X轴刻度值
 * @param yLabels Y轴刻度值
 * @param values  数据
 *
 */
- (void)reloadWithXLabels:(NSArray *)xLabels yLabels:(NSArray *)yLabels values:(NSArray *)values {
    
    // X轴刻度值
    for (NSUInteger i = 0; i < xLabels.count; i++) {
        
        CATextLayer *textLayer = self.xLayers[i];
        textLayer.string = xLabels[i];
    }
    // Y轴刻度值
    for (NSUInteger i = 0; i < yLabels.count; i++) {
        
        CATextLayer *textLayer = self.yLayers[i];
        textLayer.string = yLabels[i];
    }
    
    // Y坐标范围
    NSValue *firstYPointValue = self.yPoints.firstObject;
    NSValue *lastYPointValue = self.yPoints.lastObject;
    CGFloat minY = [firstYPointValue CGPointValue].y;
    CGFloat maxY = [lastYPointValue CGPointValue].y;
    
    // 值范围
    double minValue = [yLabels.lastObject doubleValue];
    double maxValue = [yLabels.firstObject doubleValue];
    
    self.valuePoints = [NSMutableArray arrayWithCapacity:values.count];
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    CGPoint startPoint = CGPointZero;
    
    for (NSUInteger i = 0; i < values.count; i++) {
        
        NSValue *xPointValue = self.xPoints[i];
        CGFloat x = [xPointValue CGPointValue].x;
        
        double value = [values[i] doubleValue];
        CGFloat y = minY + (maxValue - value) / (maxValue - minValue) * (maxY - minY);
        
        if (i == 0) {
            
            startPoint = CGPointMake(x, y);
            [bezierPath moveToPoint:startPoint];
        } else {
            
            CGPoint endPoint = CGPointMake(x, y);
            CGPoint midPoint = [self midPointForStartPoint:startPoint endPoint:endPoint];
            
            [bezierPath addQuadCurveToPoint:midPoint controlPoint:[self controlPointForStartPoint:midPoint endPoint:startPoint]];
            [bezierPath addQuadCurveToPoint:endPoint controlPoint:[self controlPointForStartPoint:midPoint endPoint:endPoint]];
            
            startPoint = endPoint;
        }
        [self.valuePoints addObject:[NSValue valueWithCGPoint:startPoint]];
    }
    self.contentLayer.path = bezierPath.CGPath;
    
    // 渐变色
    if (![self.layer.sublayers containsObject:self.gradientLayer]) {
        
        [self.layer insertSublayer:_gradientLayer atIndex:0];
    }
    CGPoint lastPoint = [self.valuePoints.lastObject CGPointValue];
    CGPoint firstPoint = [self.valuePoints.firstObject CGPointValue];
    [bezierPath addLineToPoint:CGPointMake(lastPoint.x, maxY)];
    [bezierPath addLineToPoint:CGPointMake(firstPoint.x, maxY)];
    ((CAShapeLayer *)self.gradientLayer.mask).path = bezierPath.CGPath;
}
  1. 给UIView添加手势,点击图表时显示对应的值。
    点击UIView时,获取手势的坐标,从而判断点击的位置是否在图表区域,获取附近的数据值点。
  • 新增加一个CALayer绘制一个圆点,凸显当前数据点。
  • 另外新增加一个CATextLayer显示当前数据点的数据。
#pragma mark - 手势处理

- (void)tapGestureHandler:(UIGestureRecognizer *)gestureRecognizer {
    
    CGPoint location = [gestureRecognizer locationInView:self];
    if (CGRectContainsPoint(self.contentRect, location) ) {
        
        CGFloat space = CGRectGetWidth(self.contentRect) / (self.xPoints.count - 1);
        NSInteger index = roundf((location.x - CGRectGetMinX(self.contentRect)) / space);
        // 选中圆点
        NSValue *pointValue = self.valuePoints[index];
        CGPoint point = [pointValue CGPointValue];
        if (![self.layer.sublayers containsObject:self.dotLayer]) {
            
            [self.layer addSublayer:self.dotLayer];
        }
        self.dotLayer.hidden = NO;
        self.dotLayer.position = CGPointMake(point.x, point.y);
        
        // 弹出文本
        CGRect frame = CGRectMake(point.x, point.y, 60, 30);
        CGFloat midX = CGRectGetMidX(self.contentRect);
        CGFloat midY = CGRectGetMidY(self.contentRect);
        if (point.x < midX) { // 弹出文本显示在图表水平中心右侧
            
            frame.origin.x += 5;
        } else { // 弹出文本显示在图表水平中心左侧
            
            frame.origin.x -= (5 + CGRectGetWidth(frame));
        }
        if (point.y < midY) { // 弹出文本显示在图表垂直中心上侧
            
            frame.origin.y += 5;
        } else { // 弹出文本显示在图表垂直中心下侧
            
            frame.origin.y -= (5 + CGRectGetHeight(frame));
        }
        if (![self.layer.sublayers containsObject:self.popupLayer]) {
            
            [self.layer addSublayer:self.popupLayer];
        }
        self.popupLayer.hidden = NO;
        self.popupLayer.string = [self attributedStringWithText:self.values[index]];
        self.popupLayer.frame = frame;
    }
}
后记

以上是绘制图表的基本思路,可基本满足绘制常见图表的需求。在实际项目中,还要根据具体的需求,比如绘制柱状图,K线图,弹出提示有多行等等,灵活运用CALayer来实现,万变不离其宗。

完整代码
//
//  ZKChartView.m
//  Layer
//
//  Created by Evan on 2018/12/22.
//  Copyright © 2018年 Evan. All rights reserved.
//

#import "ZKChartView.h"

@interface ZKChartView ()

@property (nonatomic, assign) CGRect contentRect;               // 图表区域
@property (nonatomic, strong) CAGradientLayer *gradientLayer;   // 渐变色
@property (nonatomic, strong) CAShapeLayer *maskLayer;          // 渐变色
@property (nonatomic, strong) CAShapeLayer *contentLayer;       // 数据线Layer
@property (nonatomic, strong) CALayer *dotLayer;                // 点击图表时,代表当前值的圆点
@property (nonatomic, strong) CATextLayer *popupLayer;          // 点击图表时,弹出的文本Layer
@property (nonatomic, strong) NSMutableArray *xLayers;          // X轴刻度Layers
@property (nonatomic, strong) NSMutableArray *yLayers;          // Y轴刻度Layers
@property (nonatomic, strong) NSMutableArray *xPoints;          // X轴刻度对应的CGPoint
@property (nonatomic, strong) NSMutableArray *yPoints;          // Y轴刻度对应的CGPoint
@property (nonatomic, strong) NSMutableArray *valuePoints;      // 数据对应的CGPoint
@property (nonatomic, copy) NSArray *values;                    // 数据

@end

@implementation ZKChartView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        
        // 图表区域
        UIEdgeInsets contentInsets = UIEdgeInsetsMake(48, 53, 34, 20);
        self.contentRect = CGRectMake(contentInsets.left,
                                      contentInsets.top,
                                      CGRectGetWidth(frame) - contentInsets.left - contentInsets.right,
                                      CGRectGetHeight(frame) - contentInsets.top - contentInsets.bottom);
        
        // 初始化X轴坐标
        [self initXLines];
        [self initXLabels];
        
        // 初始化Y轴坐标
        [self initYLines];
        [self initYLabels];
    }
    return self;
}

/**
 * 绘制图表
 *
 * @param xLabels X轴刻度值
 * @param yLabels Y轴刻度值
 * @param values  数据
 *
 */
- (void)reloadWithXLabels:(NSArray *)xLabels yLabels:(NSArray *)yLabels values:(NSArray *)values {
    
    // 禁用手势
    self.userInteractionEnabled = NO;
    
    // 数据校验
    if (xLabels.count != self.xLayers.count ||
        yLabels.count != self.yLayers.count ||
        values.count != self.xPoints.count) {
        
        return;
    }
    
    // X轴刻度值
    for (NSUInteger i = 0; i < xLabels.count; i++) {
        
        CATextLayer *textLayer = self.xLayers[i];
        textLayer.string = xLabels[i];
    }
    
    // Y轴刻度值
    for (NSUInteger i = 0; i < yLabels.count; i++) {
        
        CATextLayer *textLayer = self.yLayers[i];
        textLayer.string = yLabels[i];
    }
    
    // Y坐标范围
    NSValue *firstYPointValue = self.yPoints.firstObject;
    NSValue *lastYPointValue = self.yPoints.lastObject;
    CGFloat minY = [firstYPointValue CGPointValue].y;
    CGFloat maxY = [lastYPointValue CGPointValue].y;
    
    // 值范围
    double minValue = [yLabels.lastObject doubleValue];
    double maxValue = [yLabels.firstObject doubleValue];
    
    self.valuePoints = [NSMutableArray arrayWithCapacity:values.count];
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    CGPoint startPoint = CGPointZero;
    
    for (NSUInteger i = 0; i < values.count; i++) {
        
        NSValue *xPointValue = self.xPoints[i];
        CGFloat x = [xPointValue CGPointValue].x;
        
        double value = [values[i] doubleValue];
        CGFloat y = minY + (maxValue - value) / (maxValue - minValue) * (maxY - minY);
        
        if (i == 0) {
            
            startPoint = CGPointMake(x, y);
            [bezierPath moveToPoint:startPoint];
        } else {
            
            CGPoint endPoint = CGPointMake(x, y);
            CGPoint midPoint = [self midPointForStartPoint:startPoint endPoint:endPoint];
            
            [bezierPath addQuadCurveToPoint:midPoint controlPoint:[self controlPointForStartPoint:midPoint endPoint:startPoint]];
            [bezierPath addQuadCurveToPoint:endPoint controlPoint:[self controlPointForStartPoint:midPoint endPoint:endPoint]];
            
            startPoint = endPoint;
        }
        [self.valuePoints addObject:[NSValue valueWithCGPoint:startPoint]];
    }
    self.contentLayer.path = bezierPath.CGPath;
    
    // 渐变色
    if (![self.layer.sublayers containsObject:self.gradientLayer]) {
        
        [self.layer insertSublayer:_gradientLayer atIndex:0];
    }
    CGPoint lastPoint = [self.valuePoints.lastObject CGPointValue];
    CGPoint firstPoint = [self.valuePoints.firstObject CGPointValue];
    [bezierPath addLineToPoint:CGPointMake(lastPoint.x, maxY)];
    [bezierPath addLineToPoint:CGPointMake(firstPoint.x, maxY)];
    ((CAShapeLayer *)self.gradientLayer.mask).path = bezierPath.CGPath;

    // 启用手势
    self.userInteractionEnabled = YES;
    if (self.gestureRecognizers.count == 0) {
        
        [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHandler:)]];
    }
    self.values = [NSArray arrayWithArray:values];
}

#pragma mark - 手势处理

- (void)tapGestureHandler:(UIGestureRecognizer *)gestureRecognizer {
    
    CGPoint location = [gestureRecognizer locationInView:self];
    if (CGRectContainsPoint(self.contentRect, location) ) {
        
        CGFloat space = CGRectGetWidth(self.contentRect) / (self.xPoints.count - 1);
        NSInteger index = roundf((location.x - CGRectGetMinX(self.contentRect)) / space);
        if (index >= self.values.count) { // 防止数组越界
            return;
        }
        
        // 选中圆点
        NSValue *pointValue = self.valuePoints[index];
        CGPoint point = [pointValue CGPointValue];
        if (![self.layer.sublayers containsObject:self.dotLayer]) {
            
            [self.layer addSublayer:self.dotLayer];
        }
        self.dotLayer.hidden = NO;
        self.dotLayer.position = CGPointMake(point.x, point.y);
        
        // 弹出文本
        CGRect frame = CGRectMake(point.x, point.y, 60, 30);
        CGFloat midX = CGRectGetMidX(self.contentRect);
        CGFloat midY = CGRectGetMidY(self.contentRect);
        if (point.x < midX) { // 弹出文本显示在图表水平中心右侧
            
            frame.origin.x += 5;
        } else { // 弹出文本显示在图表水平中心左侧
            
            frame.origin.x -= (5 + CGRectGetWidth(frame));
        }
        if (point.y < midY) { // 弹出文本显示在图表垂直中心上侧
            
            frame.origin.y += 5;
        } else { // 弹出文本显示在图表垂直中心下侧
            
            frame.origin.y -= (5 + CGRectGetHeight(frame));
        }
        if (![self.layer.sublayers containsObject:self.popupLayer]) {
            
            [self.layer addSublayer:self.popupLayer];
        }
        self.popupLayer.hidden = NO;
        self.popupLayer.string = [self attributedStringWithText:self.values[index]];
        self.popupLayer.frame = frame;
        
        // 3秒后隐藏选中远点及弹出文本
        [UIView cancelPreviousPerformRequestsWithTarget:self selector:@selector(autoHidePopup) object:nil];
        [self performSelector:@selector(autoHidePopup) withObject:nil afterDelay:3];
    }
}

// 3秒后隐藏选中远点及弹出文本
- (void)autoHidePopup {
    
    self.dotLayer.hidden = YES;
    self.popupLayer.hidden = YES;
}

#pragma mark - 坐标系

// X轴等分线
- (void)initXLines {
    
    NSUInteger lineCount = 7;                   // 等分线数量
    UIColor *lineColor = [UIColor colorWithRed:214/255.0 green:214/255.0 blue:214/255.0 alpha:1.0]; // 等分线颜色
    CGFloat lineWidth = 1.5;                    // 等分线宽度
    CGFloat lineHeight = 4;                     // 等分线高度
    CGFloat padding = 4;                        // 等分线离图表内容底部的高度
    
    CGFloat lineSpace = CGRectGetWidth(self.contentRect) / (lineCount - 1);
    self.xPoints = [NSMutableArray arrayWithCapacity:lineCount];
    
    CGFloat startX = CGRectGetMinX(self.contentRect);
    CGFloat startY = CGRectGetMaxY(self.contentRect) + padding;
    CGFloat endY = startY + lineHeight;
    
    CAShapeLayer *xLayer = [CAShapeLayer layer];
    for (NSInteger i = 0; i < lineCount; i++) {
        
        CGPoint startPoint = CGPointMake(startX + lineSpace * i, startY);
        CGPoint endPoint = CGPointMake(startPoint.x, endY);
        
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:startPoint];
        [path addLineToPoint:endPoint];
        
        CAShapeLayer *lineLayer = [CAShapeLayer layer];
        lineLayer.fillColor = [UIColor clearColor].CGColor;
        lineLayer.strokeColor = lineColor.CGColor;
        lineLayer.lineWidth = lineWidth;
        lineLayer.path = path.CGPath;
        lineLayer.lineJoin = kCALineJoinRound;
        
        [xLayer addSublayer:lineLayer];
        
        // 记录
        [self.xPoints addObject:[NSValue valueWithCGPoint:startPoint]];
    }
    
    [self.layer addSublayer:xLayer];
}

// Y轴等分线
- (void)initYLines {
    
    NSUInteger lineCount = 5;   // 等分线数量
    UIColor *lineColor = [UIColor colorWithRed:124/255.0 green:133/255.0 blue:138/255.0 alpha:1.0]; // 等分线颜色
    CGFloat lineWidth = 0.5;    // 等分线宽度
    
    CGFloat lineSpace = CGRectGetHeight(self.contentRect) / (lineCount - 1);
    self.yPoints = [NSMutableArray arrayWithCapacity:lineCount];
    
    CGFloat startX = CGRectGetMinX(self.contentRect);
    CGFloat endX = CGRectGetMaxX(self.contentRect);
    CGFloat startY = CGRectGetMinY(self.contentRect);
    
    CALayer *yLayer = [CALayer layer];
    for (NSInteger i = 0; i < lineCount; i++) {
        
        CGPoint startPoint = CGPointMake(startX, startY + lineSpace * i);
        CGPoint endPoint = CGPointMake(endX, startPoint.y);
        
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:startPoint];
        [path addLineToPoint:endPoint];
        
        CAShapeLayer *lineLayer = [CAShapeLayer layer];
        lineLayer.fillColor = [UIColor clearColor].CGColor;
        lineLayer.strokeColor = lineColor.CGColor;
        lineLayer.lineWidth = lineWidth;
        lineLayer.path = path.CGPath;
        
        [yLayer addSublayer:lineLayer];
        
        // 记录
        [self.yPoints addObject:[NSValue valueWithCGPoint:startPoint]];
    }
    
    [self.layer addSublayer:yLayer];
}

// X轴刻度值
- (void)initXLabels {
    
    UIFont *font = [UIFont systemFontOfSize:8 weight:UIFontWeightLight]; // 文字字体
    self.xLayers = [NSMutableArray arrayWithCapacity:self.xPoints.count];
    
    for (NSUInteger i = 0; i < self.xPoints.count; i++) {
        
        NSValue *value = self.xPoints[i];
        CGPoint point = [value CGPointValue];
        
        CATextLayer *textLayer = [self textLayerWithFont:font];
        textLayer.frame = CGRectMake(point.x - 12, point.y + 10, 24, 10); // frame设定是关键
        
        [self.layer addSublayer:textLayer];
        [self.xLayers addObject:textLayer];
    }
}

// Y轴刻度值
- (void)initYLabels {
    
    UIFont *font = [UIFont systemFontOfSize:8 weight:UIFontWeightLight]; // 文字字体
    self.yLayers = [NSMutableArray arrayWithCapacity:self.yPoints.count];
    
    for (NSUInteger i = 0; i < self.yPoints.count; i++) {
        
        NSValue *value = self.yPoints[i];
        CGPoint point = [value CGPointValue];
        
        CATextLayer *textLayer = [self textLayerWithFont:font];
        textLayer.frame = CGRectMake(0, point.y - 5, 49, 10); // frame设定是关键
        
        [self.layer addSublayer:textLayer];
        [self.yLayers addObject:textLayer];
    }
}

#pragma mark - Getter

- (CAGradientLayer *)gradientLayer {
    if (!_gradientLayer) {
        
        _gradientLayer =  [CAGradientLayer layer];
        _gradientLayer.frame = self.bounds;
        _gradientLayer.colors = @[ (id)[[[UIColor redColor] colorWithAlphaComponent:0.25] CGColor],
                                   (id)[[[UIColor grayColor] colorWithAlphaComponent:0.25] CGColor]];
        _gradientLayer.mask = [CAShapeLayer layer];
    }
    return _gradientLayer;
}

- (CAShapeLayer *)contentLayer {
    if (!_contentLayer) {
        
        UIColor *lineColor = [UIColor colorWithRed:215/255.0 green:45/255.0 blue:43/255.0 alpha:1.0]; // 线颜色
        CGFloat lineWidth = 1.5; // 线宽度
        
        _contentLayer = [CAShapeLayer layer];
        _contentLayer.fillColor = [UIColor clearColor].CGColor;
        _contentLayer.strokeColor = lineColor.CGColor;
        _contentLayer.lineWidth = lineWidth;
        
        [self.layer addSublayer:_contentLayer];
    }
    return _contentLayer;
}

- (CATextLayer *)popupLayer {
    if (!_popupLayer) {
        
        UIFont *font = [UIFont systemFontOfSize:10 weight:UIFontWeightBold];
        UIColor *backgroundColor = [UIColor colorWithRed:240/255.0 green:128/255.0 blue:128/255.0 alpha:1.0];
        
        _popupLayer = [self textLayerWithFont:font];
        _popupLayer.backgroundColor = backgroundColor.CGColor;
        _popupLayer.cornerRadius = 2;
    }
    return _popupLayer;
}

- (CALayer *)dotLayer {
    if (!_dotLayer) {
        
        UIColor *backgroundColor = [UIColor colorWithRed:240/255.0 green:128/255.0 blue:128/255.0 alpha:1.0];
        
        _dotLayer = [CALayer layer];
        _dotLayer.backgroundColor = backgroundColor.CGColor;
        _dotLayer.bounds = CGRectMake(0, 0, 8, 8);
        _dotLayer.cornerRadius = 4;
    }
    return _dotLayer;
}

#pragma mark - 辅助方法

// textLayer
- (CATextLayer *)textLayerWithFont:(UIFont *)font {
    
    CFStringRef fontName = (__bridge CFStringRef)font.fontName;
    CGFontRef fontRef = CGFontCreateWithFontName(fontName);
    
    CATextLayer *textLayer = [CATextLayer layer];
    textLayer.font = fontRef;
    textLayer.fontSize = font.pointSize;
    textLayer.foregroundColor = [UIColor whiteColor].CGColor;
    textLayer.wrapped = YES;
    textLayer.truncationMode = kCATruncationEnd;
    textLayer.alignmentMode = kCAAlignmentCenter;
    textLayer.allowsFontSubpixelQuantization = YES;
    textLayer.contentsScale = [UIScreen mainScreen].scale;
    
    CGFontRelease(fontRef);
    
    return textLayer;
}

// 计算两个点的中点
- (CGPoint)midPointForStartPoint:(CGPoint)p1 endPoint:(CGPoint)p2 {
    
    return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}

// 计算两个点的控制点
- (CGPoint)controlPointForStartPoint:(CGPoint)p1 endPoint:(CGPoint)p2 {
    
    CGPoint controlPoint = [self midPointForStartPoint:p1 endPoint:p2];
    CGFloat diffY = fabs(p2.y - controlPoint.y);
    
    if (p1.y < p2.y) {
        
        controlPoint.y += diffY;
    } else if (p1.y > p2.y) {
        
        controlPoint.y -= diffY;
    }
    return controlPoint;
}

// 弹出文本样式
- (NSAttributedString *)attributedStringWithText:(NSString *)text {
    
    NSDictionary *attributes = @{ NSFontAttributeName            : [UIFont systemFontOfSize:10 weight:UIFontWeightBold],
                                  NSForegroundColorAttributeName : [UIColor whiteColor],
                                  NSBaselineOffsetAttributeName  : @(-10) };
    return [[NSAttributedString alloc] initWithString:text attributes:attributes];
}

@end

你可能感兴趣的:(iOS 一个UIView搞定图表)