美文网首页iOS程序猿
iOS 一个UIView搞定图表

iOS 一个UIView搞定图表

作者: 少东_SH | 来源:发表于2018-12-06 17:28 被阅读20次
    效果图

    在.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搞定图表

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