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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • 1 CALayer IOS SDK详解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi阅读 5,150评论 3 23
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,103评论 4 62
  • 1、15分钟的神奇力量 詹森是一家诊所的办公室经理,舒尔茨是诊所的医生兼所长。尽管忙得不可开交,舒尔茨医生每星期却...
    巧栗子阅读 1,272评论 0 0
  • (八) 在京城,你若拉住一个人让他谈谈四皇子李弘,那人可能憋死了也不知道该如何评价。 这个四皇子,生在皇家却不学无...
    江旅阅读 446评论 0 0
  • 今天吃了一颗山楂,是经理发的,一人一颗,同事都放在桌子上没吃,我先拿起来放到嘴里,可能是煮过的,咬一口软软的...
    原来是你给我的奶酪阅读 352评论 0 1