IOS端K线系列之K线-整体搭建

标签: ios  k线图  CAShapeLay  股票

k线系列目录

查看目录请点击这儿


前言

当写完了所有需要使用的素材类后,我们开始搭建一个比较完整的K线Demo。

它包含以下功能:

1、可以展示蜡烛图
2、可以展示OHLC图
3、可以左右滑动
4、可以长按出现十字叉
5、有基本的价格和日期区间展示

GO

在上几篇文章中,我们已经知道如何绘制蜡烛、边框、OHLC等。所以在这里,可以直接使用已经写好的类。

绘制边框

首页,我们先绘制一个包含主副图的边框:

/**
 绘制边框
 */
- (void)drawBorder
{
    //设置主图、主图指标、副图、副图指标rect
    _mainIndexRect = CGRectMake(0, 0, CGRectGetWidth(self.frame), mainIndexH);
    _mainRect = CGRectMake(0, mainIndexH, CGRectGetWidth(self.frame), (CGRectGetHeight(self.frame) - (mainIndexH + accessoryIndexH + dateH)) * mainFrameScale);
    _accessoryIndexRect = CGRectMake(0, mainIndexH + CGRectGetHeight(_mainRect)+dateH, CGRectGetWidth(self.frame), accessoryIndexH);
    _accessoryRect = CGRectMake(0, mainIndexH + CGRectGetHeight(_mainRect)+dateH+accessoryIndexH, CGRectGetWidth(self.frame), (CGRectGetHeight(self.frame) - (mainIndexH + accessoryIndexH + dateH)) * (1-mainFrameScale));


    CAShapeLayer *borderLayer = [CAShapeLayer layer];
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];

    [path moveToPoint:CGPointMake(0, mainIndexH)];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), mainIndexH)];

    [path moveToPoint:CGPointMake(0, CGRectGetMaxY(_mainRect))];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMaxY(_mainRect))];

    [path moveToPoint:CGPointMake(0, CGRectGetMinY(_accessoryIndexRect))];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMinY(_accessoryIndexRect))];

    [path moveToPoint:CGPointMake(0, CGRectGetMinY(_accessoryRect))];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMinY(_accessoryRect))];

    float mainUnitH = CGRectGetHeight(_mainRect) / 4.f;
    float mainUnitW = CGRectGetWidth(_mainRect) / 4.f;

    for (int idx = 1; idx <= 3; idx++)
    {
        //画3条横线
        [path moveToPoint:CGPointMake(0, mainIndexH + mainUnitH * idx)];
        [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), mainIndexH + mainUnitH * idx)];

        //画3条竖线
        [path moveToPoint:CGPointMake(idx * mainUnitW, mainIndexH)];
        [path addLineToPoint:CGPointMake(idx * mainUnitW, CGRectGetMaxY(_mainRect))];

        //画3条竖线
        [path moveToPoint:CGPointMake(idx * mainUnitW, CGRectGetMinY(_accessoryRect))];
        [path addLineToPoint:CGPointMake(idx * mainUnitW, CGRectGetMaxY(_accessoryRect))];
    }

    float accessoryUnitH = CGRectGetHeight(_accessoryRect) / 2.f;
    [path moveToPoint:CGPointMake(0, CGRectGetMaxY(_accessoryRect) - accessoryUnitH)];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMaxY(_accessoryRect) - accessoryUnitH)];

    borderLayer.path = path.CGPath;
    borderLayer.lineWidth = 0.5f;
    borderLayer.strokeColor = [UIColor colorWithRed:222.f/255.f green:222.f/255.f blue:222.f/255.f alpha:1.f].CGColor;
    borderLayer.fillColor = [UIColor clearColor].CGColor;

    [self.layer addSublayer:borderLayer];

}

效果如下:
边框

导入数据

边框绘制完以后,我们把k线数据转换为坐标点。在转换之前,还需要寻找当前在屏幕上展示数据的极限值,也就是最大最小值。

这里要注意,因为当前屏幕中一般最多显示60个左右的蜡烛数量,但是某一个周期K线的蜡烛数量一般是几百个,所以在K线数据转为模型数据后,需要有一个起始索引来标识当前展示的数据范围。

寻找极限值:

//求出最大最小值
    _min = (float)INT32_MAX;
    _max = (float)INT32_MIN;
    for (int idx=_startIndex; idx<_endIndex; idx++)
    {
        YKKLineModel *model = self.kLineModelArr[idx];
        if (_min > model.low)
        {
            _min = model.low;
        }
        if (_max < model.high)
        {
            _max = model.high;
        }
    }

把数据转换为坐标点:

    [self.displayPointArr removeAllObjects];
    //每根蜡烛的宽度
    float candleW = CGRectGetWidth(_mainRect) / candleCount;
    for (int idx = _startIndex; idx<_endIndex; idx++)
    {
        YKKLineModel *model = self.kLineModelArr[idx];
        float x = CGRectGetMinX(_mainRect) + candleW * (idx - (_startIndex - 0));

        CGPoint hPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.high  - _min)/unitValue));
        CGPoint lPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.low   - _min)/unitValue));
        CGPoint oPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.open  - _min)/unitValue));
        CGPoint cPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.close - _min)/unitValue));
        [_displayPointArr addObject:[YKCandlePointModel candlePointModelWithOpoint:oPoint
                                                                            Hpoint:hPoint
                                                                            Lpoint:lPoint
                                                                            Cpoint:cPoint]];
    }

绘制蜡烛/OHLC

导入数据并且转换为坐标点以后,接下来开始绘制。

绘制蜡烛:

    //每根蜡烛的宽度
    float candleW = CGRectGetWidth(_mainRect) / candleCount;

    for (int idx = 0; idx< candleCount; idx++)
    {
        YKCandlePointModel *model = pointModelArr[idx];
        CAShapeLayer *layer = [CAShapeLayer getCandleLayerWithPointModel:model candleW:candleW];

        [self.candleLayer addSublayer:layer];
    }

    [self.layer addSublayer:self.candleLayer];

绘制OHLC:

    //每根OHLC的宽度
    float candleW = CGRectGetWidth(_mainRect) / candleCount;

    for (int idx = 0; idx< candleCount; idx++)
    {
        YKCandlePointModel *model = pointModelArr[idx];
        CAShapeLayer *layer = [CAShapeLayer getOHLCLayerWithPointModel:model candleW:candleW];

        [self.ohlcLayer addSublayer:layer];
    }

    [self.layer addSublayer:self.ohlcLayer];

效果如下:

蜡烛

OHLC

绘制价格、日期区间

蜡烛绘制完以后,我们还剩下价格、日期。价格是指左侧的5个价格的区间标识,从上往下降序排列;日期为下方5个日期的区间标识,每一个标识都是距离当前点最近的点的日期。

绘制左侧价格:

    float unitPrice = (_max - _min) / 4.f;
    float unitH = CGRectGetHeight(_mainRect) / 4.f;

    //求得价格rect
    NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:9.f]};
    CGRect priceRect = [self rectOfNSString:[NSString stringWithFormat:@"%.2f", _max] attribute:attribute];

    for (int idx = 0; idx < 5; idx++)
    {
        float height = 0.f;
        if (idx == 4)
        {
            height = idx * unitH - CGRectGetHeight(priceRect);
        } else
        {
            height = idx * unitH;
        }
        CGRect rect = CGRectMake(CGRectGetMinX(_mainRect),
                                 CGRectGetMinY(_mainRect) + height,
                                 CGRectGetWidth(priceRect),
                                 CGRectGetHeight(priceRect));
        //计算价格
        NSString *str = [NSString stringWithFormat:@"%.2f", _max - idx * unitPrice];
        CATextLayer *layer = [CATextLayer getTextLayerWithString:str
                                                       textColor:[UIColor blackColor]
                                                        fontSize:9.f
                                                 backgroundColor:[UIColor clearColor]
                                                           frame:rect];

        [self.leftPriceLayer addSublayer:layer];
    }

    [self.layer addSublayer:self.leftPriceLayer];

绘制日期(这里要注意,因为使用的Demo数据周期为天,所以日期也具体到天):

    NSMutableArray *kLineDateArr = [NSMutableArray array];
    int unitCount = candleCount / 4;
    for (int idx=0; idx<5; idx++)
    {
        YKKLineModel *model = self.kLineModelArr[_startIndex + idx * unitCount];

        NSDate *detaildate = [NSDate dateWithTimeIntervalSince1970:model.timeStamp];
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat:@"YYYY-MM-dd"];
        NSString *dateStr = [dateFormatter stringFromDate:detaildate];

        [kLineDateArr addObject:dateStr];
    }

    NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:9.f]};
    CGRect strRect = [self rectOfNSString:@"0000-00-00" attribute:attribute];
    float strW = CGRectGetWidth(strRect);
    float strH = CGRectGetHeight(strRect);

    float unitW = CGRectGetWidth(_mainRect) / 4;

    //循环绘制坐标点
    for (int idx = 0; idx < kLineDateArr.count; idx++)
    {
        CATextLayer *textLayer = nil;

        if (idx == kLineDateArr.count-1)
        {//最后一个
            CGRect rect = CGRectMake(idx * unitW - strW, CGRectGetMaxY(_mainRect), strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }else if(idx == 0)
        {//第一个
            CGRect rect = CGRectMake(idx * unitW, CGRectGetMaxY(_mainRect), strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }else
        {//中间
            CGRect rect = CGRectMake(idx * unitW - strW/2, CGRectGetMaxY(_mainRect), strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }

        [self.bottomDateLayer addSublayer:textLayer];
    }

    [self.layer addSublayer:self.bottomDateLayer];

效果如下:

区间标识

添加左右滑动

在以前我们讨论过滑动偏移量的获取方式,这里就暂且通过添加手势来获取偏移量。使用长按手势来做十字叉效果,使用拖动手势来做左右滑动效果。

当检测到用户长按时,获取坐标点然后转换为柱子索引,再绘制十字叉,左侧和下侧展示当前索引的数据。当用户抬起手指时,可以选择及时清除掉十字叉,也可以加一个延时清除。

当检测到用户拖动时,用偏移量的正负来判断用户是向左还是向右拖动。每次检测到拖动后,获取到偏移量,因为这个偏移量不是太线性,所以添加一个范围的判断。拿到偏移量以后,再更新展示数据的起始索引值,然后再更新视图。

添加手势:

    //添加左右拖动手势
    UIPanGestureRecognizer *panG = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
    [_kLineView addGestureRecognizer:panG];

    //添加长按手势
    UILongPressGestureRecognizer *longG = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(kLineLongGestureAction:)];
    longG.minimumPressDuration = 0.5f;
    longG.numberOfTouchesRequired = 1;
    [_kLineView addGestureRecognizer:longG];

响应手势:

/**
 K线响应长按手势

 @param longGesture 手势对象
 */
- (void)kLineLongGestureAction:(UILongPressGestureRecognizer *)longGesture
{
    if (longGesture.state == UIGestureRecognizerStateBegan || longGesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint point = [longGesture locationInView:_kLineView];

        float x = 0.f;
        if (point.x < 0.f)
        {
            x = 0.f;
        }else if (point.x > CGRectGetWidth(_kLineView.frame))
        {
            x = CGRectGetWidth(_kLineView.frame)-1;
        }else
        {
            x = point.x;
        }
        //当长按滑动时,每滑动一次话会重新刷新十字叉
        [_kLineView drawCrossViewWithX:x];
    }else
    {
        //当手指抬起时,及时把十字叉取消掉
        [_kLineView clearCrossViewLayer];
    }
}

/**
 响应拖动手势

 @param panGesture 手势对象
 */
- (void)panGestureAction:(UIPanGestureRecognizer *)panGesture
{
    CGPoint point = [panGesture translationInView:_kLineView];
    float offset =  point.x - kLineGlobalOffset;
    if (panGesture.state == UIGestureRecognizerStateChanged && ABS(offset) > 3)
    {
        if (offset > 0)
        {
            if (ABS(offset) > 20)
            {
                [_kLineView dragRightOffsetcount:5];

            } else if(ABS(offset) > 6)
            {
                [_kLineView dragRightOffsetcount:2];

            } else
            {
                [_kLineView dragRightOffsetcount:1];
            }
        }else
        {
            if (ABS(offset) > 20)
            {
                [_kLineView dragLeftOffsetcount:5];

            } else if(ABS(offset) > 6)
            {
                [_kLineView dragLeftOffsetcount:2];

            } else
            {
                [_kLineView dragLeftOffsetcount:1];
            }
        }
        kLineGlobalOffset = point.x;
    }

    if (panGesture.state == UIGestureRecognizerStateEnded ||
        panGesture.state == UIGestureRecognizerStateCancelled ||
        panGesture.state == UIGestureRecognizerStateFailed)
    {
        kLineGlobalOffset= 0.f;
    }
}

效果如下:

OHLC线

Demo源码下载

至此,我们已经把K线主图大部分的功能搭建完毕。Demo源码点击这里下载。下篇文章,会说到主副图指标的一些事,敬请期待。

版权声明:本文为yunkai666原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yunkai666/article/details/72827630

智能推荐

【Sublime】使用 Sublime 工具时运行python文件

使用 Sublime 工具时报Decode error - output not utf-8解决办法   在菜单中tools中第四项编译系统 内最后一项增添新的编译系统 自动新建 Python.sublime-build文件,并添加"encoding":"cp936"这一行,保存即可 使用python2 则注释encoding改为utf-8 ctr...

java乐观锁和悲观锁最底层的实现

1. CAS实现的乐观锁 CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的,也可以理解为自旋锁 JUC是指import java.util.concurrent下面的包, 比如:import java.util.concurrent.atomic.AtomicInteger; 最终实现是汇编指令:lock...

Python 中各种imread函数的区别与联系

  原博客:https://blog.csdn.net/renelian1572/article/details/78761278 最近一直在用python做图像处理相关的东西,被各种imread函数搞得很头疼,因此今天决定将这些imread总结一下,以免以后因此犯些愚蠢的错误。如果你正好也对此感到困惑可以看下这篇总结。当然,要了解具体的细节,还是应该 read the fuc...

用栈判断一个字符串是否平衡

注: (1)本文定义:左符号:‘(’、‘[’、‘{’…… 右符号:‘)’、‘]’、‘}’……. (2)所谓的字符串的符号平衡,是指字符串中的左符号与右符号对应且相等,如字符串中的如‘(&r...

JAVA环境变量配置

位置 计算机->属性->高级系统设置->环境变量 方式一 用户变量新建path 系统变量新建classpath 方式二 系统变量 新建JAVA_HOME,值为JDK路径 编辑path,前加 方式三 用户变量新建JAVA_HOME 此路径含lib、bin、jre等文件夹。后运行tomcat,eclipse等需此变量,故最好设。 用户变量编辑Path,前加 系统可在任何路径识别jav...

猜你喜欢

常用的伪类选择器

CSS选择器众多 CSS选择器及权重计算 最常用的莫过于类选择器,其它的相对用的就不会那么多了,当然属性选择器和为类选择器用的也会比较多,这里我们就常用的伪类选择器来讲一讲。 什么是伪类选择器? CSS伪类是用来添加一些选择器的特殊效果。 常用的为类选择器 状态伪类 我们中最常见的为类选择器就是a标签(链接)上的为类选择器。 当我们使用它们的时候,需要遵循一定的顺序问题,否则将可能出现bug 注意...

ButterKnife的使用介绍及原理探究(六)

前面分析了ButterKnife的源码,了解其实现原理,那么就将原理运用于实践吧。 github地址:       点击打开链接 一、自定义注解 这里为了便于理解,只提供BindView注解。 二、添加注解处理器 添加ViewInjectProcessor注解处理器,看代码, 这里分别实现了init、getSupportedAnnotationTypes、g...

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。考试复习题库1|要求:只能使用单字符比较操作。

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。 要求只能使用单字符比较操作。 参考代码: 实验结果截图:...

小demo:slideDown()实现二级菜单栏下拉效果

效果如下,鼠标经过显示隐藏的二级菜单栏 但是这样的时候会存在一个问题,就是鼠标快速不停移入移出会导致二级菜单栏闪屏现象,一般需要使用stop()来清除事件  ...

基于docker环境的mysql主从复制

1、安装docker 可以参考之前的博客,之前写过了~ 2、拉取mysql镜像 3、创建mysql01和mysql02实例 主: 从: 4、进入容器修改配置 1)修改主数据库配置 进入主数据库容器 切换到 etc/mysql/目录下 查看可以看到my.cnf文件,使用vim编辑器打开,但是需要提前安装 安装vim命令: 安装成功后,修改my.cnf文件 新增配置后的my.cnf: binlog 日...