iOS 开发 iOS开发技巧iOS 开发

K线三方库__ZXKline

2017-10-25  本文已影响1114人  石头人R
Animation.gif

github__ZXKline

1.简介篇

fullScreen1.png fullScreen2.png UI1.png UI2.png

2.原理篇

2.1 tableView作为画布依耐

为什么选择了tableView

需要解决的问题:变纵向滚动为纵向滚动

旋转.png
    .
    .
    self.tableView.transform = CGAffineTransformMakeRotation(-M_PI/2);
    .
    .
    [self.view addSubview:self.tableView];
    .
    .
    [self.tableView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo((width-height)/2);
        make.top.mas_equalTo(-(width-height)/2);
        make.width.mas_equalTo(height);
        make.height.mas_equalTo(width);
    }];  

2.2 缩放

缩放有度

- (void)pinchAction:(UIPinchGestureRecognizer *)sender
{ 
    static CGFloat oldScale = 1.0f;
    CGFloat difValue = sender.scale - oldScale;
    NSLog(@"difValue=====%f",difValue);
    NSLog(@"oldScale=====%f",oldScale);
    if (ABS(difValue)>StockChartScaleBound) {
    
    CGFloat oldKlineWidth = self.candleWidth;
    // NSLog(@"原来的index%ld",oldNeedDrawStartIndex);
    self.candleWidth = oldKlineWidth * ((difValue > 0) ? (1+StockChartScaleFactor):(1-StockChartScaleFactor));
    oldScale = sender.scale;
    if (self.candleWidth < scale_MinValue) {
        
        self.candleWidth = scale_MinValue;
    }else if (self.candleWidth > scale_MaxValue)
    {
        self.candleWidth = scale_MaxValue;
    }
  }
}

定点缩放

//这句话达到让tableview在缩放的时候能够保持缩放中心点不变;
//实现原理:在放大缩小的时候,计算出变化后和变化前中心点的距离,然后为了保持中心点的偏移值始终保持不变,就直接在原来的偏移上加减变换的距离
//ceil(centerPoint.y/oldKlineWidth)中心点前面的cell个数
//self.rowHeight-oldKlineWidth每个cell的高度的变化
CGFloat pinchOffsetY  = ceil(centerPoint.y/oldKlineWidth)*(self.candleWidth-oldKlineWidth)+oldNeedDrawStartPointY;
if (pinchOffsetY<0) {
    
    pinchOffsetY = 0;
}
if (pinchOffsetY+self.subViewWidth>self.kLineModelArr.count*self.candleWidth) {
    
    pinchOffsetY = self.kLineModelArr.count*self.candleWidth - self.subViewWidth;
}

[self.tableView setContentOffset:CGPointMake(0, pinchOffsetY)];

2.3 实现原理

宏观布局

两个关键参数:

坐标相关换算

蜡烛绘制

CAShapeLayer+UIBeizerPath

2.4 Socket数据结算

详见ZXSocketDataReformer
针对服务器返回的数据格式:@"时间戳,实时价格";我们需要利用这一个个的数据自己构建蜡烛模型;

2.5 实时绘制

考虑如下情况:

实时绘制.png

代码大概是这样的 :

- (void)handleNewestCellWhenScrollToBottomWithNewKlineModel:(KlineModel *)klineModel

{
        
     //==0的时候需要插入一个新的cell;否则只需要刷新最后一个cell
    if (self.isNew) {
        
        KlineModel *newsDataModel =  [self calulatePositionWithKlineModel:klineModel];
        [self.kLineModelArr addObject:newsDataModel];
        
        double oldMax = self.maxAssert;
        double oldMin = self.minAssert;
        
        
        [self calculateNeedDrawKlineArr];
        [self calculateMaxAndMinValueWithNeedDrawArr:self.needDrawKlineArr];
        
        //不等的话就重绘
        if (oldMax<self.maxAssert||oldMin>self.minAssert) {
            
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                [self.tableView setContentOffset:CGPointMake(0, (self.kLineModelArr.count-self.needDrawKlineCount)*self.candleWidth+(self.needDrawKlineCount*self.candleWidth-self.subViewWidth))];
            });
            
            [self drawTopKline];
            
        }else{
            //否则就插入
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.kLineModelArr.count-1 inSection:0];
            dispatch_async(dispatch_get_main_queue(), ^{
                
                //先增加  再偏移
                [self.tableView beginUpdates];
                [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                [self.tableView endUpdates];
                [self.tableView setContentOffset:CGPointMake(0, (self.kLineModelArr.count-self.needDrawKlineCount)*self.candleWidth+(self.needDrawKlineCount*self.candleWidth-self.subViewWidth))];
            });
            
            [self delegateToReturnKlieArr];
        }
        
    }else{
        
        
        KlineModel *newsDataModel =  [self calulatePositionWithKlineModel:klineModel];
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.kLineModelArr.count-1 inSection:0];
        
        [self.kLineModelArr replaceObjectAtIndex:self.kLineModelArr.count-1 withObject:newsDataModel];
        
        
        CGFloat oldMax = self.maxAssert;
        CGFloat oldMin = self.minAssert;
        
        
        [self calculateNeedDrawKlineArr];
        [self calculateMaxAndMinValueWithNeedDrawArr:self.needDrawKlineArr];
        //如果计算出来的最新的极值不在上一次计算的极值直接的话就重绘,否则就刷新最后一个即可
        if (oldMax<self.maxAssert||oldMin>self.minAssert) {
            
            [self drawTopKline];
            
        }else{
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                [self.tableView beginUpdates];
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                [self.tableView endUpdates];
                [self delegateToReturnKlieArr];
            });
            
        }
        
    }

}

实际使用过程中在insert或者reloadrows的时候,偶尔会出现崩溃,暂时还没解决,索性改为了直接重绘全屏了(我内心也是拒绝的),若是你们也不甘心让它直接重绘,可到--ZXMainView.m--- (void)handleNewestCellWhenScrollToBottomWithNewKlineModel:(KlineModel *)klineModel;打开注释的方法,终结了它;

3.使用篇

3.1 基本使用

3.2 使用注意

3.2.1 历史数据转模型

(详见Reformer---ZXCandleDataReformer)
本地历史数据格式为:

/*
 @[@"时间戳,收盘价,开盘价,最高价,最低价,成交量",
 @"时间戳,收盘价,开盘价,最高价,最低价,成交量",
 @"时间戳,收盘价,开盘价,最高价,最低价,成交量",
 @"...",
 @"..."];
 */  

相应的模型转换格式为:

- (NSArray<KlineModel *>*)transformDataWithDataArr:(NSArray *)dataArr currentRequestType:(NSString *)currentRequestType
{
    self.currentRequestType = currentRequestType;
    //修改数据格式  →  ↓↓↓↓↓↓↓终点到啦↓↓↓↓↓↓↓↓↓  ←
    NSMutableArray *tempArr = [NSMutableArray array];
    __weak typeof(self) weakSelf = self;
    [dataArr enumerateObjectsUsingBlock:^(NSString *dataStr, NSUInteger idx, BOOL * _Nonnull stop) {
        
        NSArray *strArr = [dataStr componentsSeparatedByString:@","];
        KlineModel *model = [KlineModel new];
        model.timestamp  = [strArr[0] integerValue];
        model.timeStr = [weakSelf setTime:strArr[0]];
        model.closePrice = [strArr[1] doubleValue];
        model.openPrice = [strArr[2] doubleValue];
        model.highestPrice = [strArr[3] doubleValue];
        model.lowestPrice = [strArr[4] doubleValue];
        if (strArr.count>=6) {
            
            model.volumn = @([strArr[5] doubleValue]);
        }else{
            model.volumn = @(0);
        }
        
        model.x = idx;
        [tempArr addObject:model];
        model = nil;
    }];
    return tempArr;
}

历史数据模型转换需要使用者根据请求历史数据的实际格式进行转换;

3.2.2 Socket数据转模型

(详见ZXSocketDataReformer)
在socket结算的时候,若需要服务器时间结合socket返回的时间共同完成一个蜡烛的时候,这里需要改为获取服务器时间;

- (void)requestServiceTime:(void(^)(NSInteger timesamp))success
{
    
        //这里Demo使用的本地时间代替;正确的应该取下面的服务器时间
        NSDate *date = [NSDate dateWithTimeIntervalSinceNow:0];
        NSTimeInterval timestamp = [date timeIntervalSince1970];
        success(timestamp);
        
        //获取服务器时间
    //    NSString *urlStr = @"服务器时间校对地址";
    //
    //    self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    //    self.manager.responseSerializer.acceptableContentTypes = [self.manager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html"];
    //    [self.manager GET:urlStr parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    //
    //        NSString *time = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
    //        success([time integerValue]);
    //        //        NSLog(@"ServiceTime=%@",time);
    //
    //    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    //
    //    }];
    
}

3.2.3 布局修改

(详见ZXHeader.h)

整体布局修改的几个宏

/**
 * 价格坐标系在右边?YES->右边;NO->左边
 */
#define PriceCoordinateIsInRight YES     

/**
 * 蜡烛的信息配置的位置:YES->单独的view显示在view顶部;NO->弹框覆盖在蜡烛上
 */
#define IsDisplayCandelInfoInTop NO

约束

布局.png

从某种角度上来说,很多约束可以不改,但是宏中的TotalHeight必须根据项目需求进行修改

3.2.4 横竖屏适配

小技巧:因为我这里横屏之后是全屏并且隐藏了状态栏和导航栏的,为了旋转之后和竖屏的其他控件互不干扰,可以将assenblyView实例添加在self.view的最顶层,然后旋转过去之后就直接将其他控件覆盖在底层

4 其他问题

  1. 关于历史k线和socket衔接处暂未进行处理, 衔接还存在误差;
  2. 未知bug?待挖掘;
  3. k线图UI很简单,除了k线没有其他定制,但是接口都是完善的,主要是觉得关乎UI部分我做得越少,通用性就越高;
  4. 感谢Star;
  5. 有任何其他问题欢迎Issues或者简书留言;
  6. 超链:
上一篇下一篇

猜你喜欢

热点阅读