我与那该死的设计师的曲线之战(iOS-Charts)

前言

先直接上图看设计师要求的图片吧

首页

看到这个首页效果的时候觉得微微有点蛋疼,但觉得应该是可以的,没多想
后来才知道是我太年轻了。。

找了各种第三方,留给自己时间不多,基本没时间自己做一个出来,所以只能借助第三方,最后决定用 Charts,毕竟是大神而且和安卓一起开发遇到问题也可以一起讨论,start 也破万。

正文

不说废话了,直接开始吧,首先由于项目决定使用的语言是 OC 语言,charts 是 swift 语言,所以只能混编,项目最低支持版本8.0

关于charts 怎么集成到 OC 代码里面,我看过简书已经写了很多了,我就不重复了,而且也没什么技术难点,无非就是拖进去就好。我没有使用 pods 毕竟需要修改源码。。想到就略蛋疼。。。

基础集成

把 charts 集成好之后就可以使用了,先编译一遍确认能够正常编译通过,通过后就可以开始使用了。先说下基本的设置吧。我是用的曲线,也就是折线的分类,charts 有很多个模块,其他模块我没有去研究,毕竟没那么多时间,如果你用storyboard 创建的,那就是新建一个 view,类的名字直接为LineChartView,
如果是代码创建,也是直接用这个类创建一个 view 然后设置 frame 添加到父类 view 上就好了。

// chartview 本身的属性设置
self.chartView.delegate = self;// 代理
    self.chartView.backgroundColor = [UIColor clearColor];//设置背景颜色
    self.chartView.descriptionText = @"";//隐藏描述文字
    self.chartView.noDataText = @"暂时没有体重数据";// 设置没有数据的显示内容
    self.chartView.legend.enabled = NO;//不显示图例说明
    self.chartView.scaleYEnabled = NO;//取消Y轴缩放
    self.chartView.scaleXEnabled = NO;//取消X轴缩放
    self.chartView.scaleEnabled = NO;// 缩放
    self.chartView.doubleTapToZoomEnabled = NO;//取消双击缩放
    self.chartView.dragDecelerationEnabled = NO;//拖拽后是否有惯性效果
    self.chartView.dragDecelerationFrictionCoef = 0;//拖拽后惯性效果的摩擦系数(0~1),数值越小,惯性越不明显
    self.chartView.rightAxis.enabled = NO;//不绘制右边轴的信息
    self.chartView.leftAxis.enabled = NO;//不绘制左边轴的信息
    self.chartView.xAxis.enabled = NO;
    self.chartView.xAxis.drawGridLinesEnabled = NO;//不绘制网络线
    self.chartView.xAxis.axisLineColor = [UIColor whiteColor];// x 轴的网络线颜色
    self.chartView.xAxis.labelPosition = XAxisLabelPositionTopInside;// X轴的位置
    self.chartView.xAxis.granularity = 1;// 间隔为1

基本上看得懂英文的都懂得这些的意思
看效果图,需要点击的时候出现一个 view,跟随点的位置,charts 本身已经实现了这个功能,名字叫BalloonMarker,但是集成的时候没有集成过来,需要自己写,或者直接在 demo 里面搜索这个类拷贝到自己的项目中去修改

// 显示气泡效果
    BalloonMarker *marker = [[BalloonMarker alloc]
                             initWithColor: [UIColor colorWithRed:0.384 green:0.800 blue:0.980 alpha:1.000]
                             font: [UIFont systemFontOfSize:12.0]
                             textColor: UIColor.whiteColor
                             insets: UIEdgeInsetsMake(8.0, 8.0, 20.0, 8.0)];
    marker.img = [UIImage imageNamed:@"marker"];
    marker.chartView = self.chartView;
    self.chartView.marker = marker;

基础设置都设置好了,接下来就可以填充数据了,charts 你大致是可以理解为这样的工作模式:

设置 chartview 的属性 -> 设置LineChartData数据 -> 最后设置各种数据显示属性

这是我第一个坑,有一些属性是需要等后面数据填充了才可以进行设置,否者会遇到各种奇怪的问题。
我们继续进行数据的填充吧,chartview 可以设置多条线,每一条线就是一个LineChartDataSet,每一条线上面的点就是ChartDataEntry,想在一条线上显示多少个点就需要多少个ChartDataEntry。

NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < self.weights.count; i++) {
    ChartDataEntry *entry = [[ChartDataEntry alloc] initWithX:i y:ww];
    entry.flag = model.weightflag;//自己增加的属性
    entry.realValue = model.weight;//也是自己增加的属性
    [arr addObject:entry];
}
LineChartDataSet *set = [[LineChartDataSet alloc] initWithValues:arr label:@""];
//对于线的各种设置
    set.drawValuesEnabled = NO;//不显示文字
    set.highlightEnabled = YES;//选中拐点,是否开启高亮效果(显示十字线)
    set.highlightColor = [UIColor clearColor];// 十字线颜色
    set.drawCirclesEnabled = YES;//是否绘制拐点
    set.cubicIntensity = 0.2;// 曲线弧度
    set.circleRadius = 5.0f;//拐点半径
    set.drawCircleHoleEnabled = NO;//是否绘制中间的空心
    set.circleHoleRadius = 4.0f;//空心的半径
    set.circleHoleColor = [UIColor whiteColor];//空心的颜色
    set.circleColors = @[[UIColor colorWithRed:0.114 green:0.812 blue:1.000 alpha:1.000]];
    set.mode = LineChartModeCubicBezier;// 模式为曲线模式
    set.drawFilledEnabled = YES;//是否填充颜色
// 设置渐变效果
[set setColor:[UIColor colorWithRed:0.114 green:0.812 blue:1.000 alpha:1.000]];//折线颜色
    NSArray *gradientColors = @[(id)[ChartColorTemplates colorFromString:@"#FFFFFFFF"].CGColor,
                                (id)[ChartColorTemplates colorFromString:@"#C4F3FF"].CGColor];
    CGGradientRef gradientRef = CGGradientCreateWithColors(nil, (CFArrayRef)gradientColors, nil);
    set.fillAlpha = 1.0f;//透明度
    set.fill = [ChartFill fillWithLinearGradient:gradientRef angle:90.0f];//赋值填充颜色对象
    CGGradientRelease(gradientRef);//释放gradientRef
// 把线放到LineChartData里面,因为只有一条线,所以集合里面放一个就好了,多条线就需要不同的 set 啦
LineChartData *data = [[LineChartData alloc] initWithDataSets:@[set]];

把线都设置好了,放在 data 里面后,还需要把 data 放在 chartview 的 data 里面,这样才算是填充了数据

self.chartView.data = [self setData]; // 加载数据

之后就进行最后的设置了

//设置最大显示值
NSMutableArray *arr = [NSMutableArray array];
for (NSUInteger i = 0; i < self.weights.count; i++) {
        FFWeightModel *model = self.weights[i];
        [arr addObject:@(model.weight)];
    }
double max = [[arr valueForKeyPath:@"@max.doubleValue"] doubleValue];
self.chartView.leftAxis.axisMaxValue = max;
//设置最小显示值
double min = [[arr valueForKeyPath:@"@min.doubleValue"] doubleValue];
self.chartView.leftAxis.axisMinValue = min;
// 设置区域显示,要显示7个数据,所以设置最大显示和最小显示
[_chartView setVisibleXRangeMaximum:6];// 最大显示
[_chartView setVisibleXRangeMinimum:6];// 最小显示

//设计师说要数据加载的时候前面留空白3个,拖到后面也是留空白3个,保证点都是在中间为准,所以又设置了留白数据
self.chartView.xAxis.axisMinimum = -3;//最前面留空白3个区域
self.chartView.xAxis.axisMaximum = self.weights.count+2.1;//后面留空3个区域,别问我为什么是2.1

//添加添加限制的线
ChartLimitLine *limitLine = [[ChartLimitLine alloc] initWithLimit:[self.userweight[@"weight_first"] doubleValue] label:[NSString stringWithFormat:@"初始%@kg",self.userweight[@"weight_first"]]];
    limitLine.lineWidth = 1;
    limitLine.lineDashLengths = @[@(5.0),@(5.0)];
    limitLine.lineColor = [UIColor colorWithRed:1.000 green:0.671 blue:0.671 alpha:1.000];
    limitLine.labelPosition = ChartLimitLabelPositionCenter;//位置
    limitLine.valueTextColor = [UIColor colorWithWhite:0.502 alpha:1.000];//label文字颜色
    limitLine.valueFont = [UIFont systemFontOfSize:12];//label字体
    
    ChartLimitLine *limitLine2 = [[ChartLimitLine alloc] initWithLimit:[self.userweight[@"weight_target"] doubleValue] label:[NSString stringWithFormat:@"目标%@kg",self.userweight[@"weight_target"]]];
    limitLine2.lineWidth = 1;
    limitLine2.lineDashLengths = @[@(5.0),@(5.0)];
    limitLine2.lineColor = [UIColor colorWithRed:0.682 green:0.925 blue:1.000 alpha:1.000];
    limitLine2.labelPosition = ChartLimitLabelPositionCenter;//位置
    limitLine2.valueTextColor = [UIColor colorWithWhite:0.502 alpha:1.000];//label文字颜色
    limitLine2.valueFont = [UIFont systemFontOfSize:12];//label字体

//把限制线添加到 chartview 里面
[self.chartView.leftAxis addLimitLine:limitLine];
[self.chartView.leftAxis addLimitLine:limitLine2];

//最后每次打开都自动高亮最后一个数据,所以需要设置
FFWeightModel *model = self.weights.lastObject;
[self.chartView highlightValueWithX:self.weights.count-1 y:model.realWeight dataSetIndex:0 callDelegate:NO];
//由于每次打开都是显示在最前面的点,所以需要移动显示的位置
[_chartView moveViewToX:self.weights.count];// 移动到那个点
//最后显示出动画
[self.chartView animateWithYAxisDuration:1];//动画

其实以上都是简单的设置,只要花时间就能设置出来,但是难点永远不是这些,是那些坑爹的需要,是那些不知道技术实现起来很难以为只是很简单的,所以需求总是天天有,只能拼命改改改T_T

说一些 charts 的一些坑吧

集成BalloonMarker的坑

我是直接拿 demo 的过来用的,如果不熟悉 swift 的同学就要注意了,你把 swift 的文件集成过来后,需要编译一次,然后你是不是想说应该怎么声明这个BalloonMarker,因为你在 import 里面没有看到这个,混编的 swift 其实全部都在一个不可见的 h 文件里面,你只需要 #import "你项目的名字-swift.h"这个文件就可以了,所有混编的 swift 都会在这里声明,不过你需要先编译一次才会在里面可以找到
对于BalloonMarker修改 view 的背景,他是直接画出来的,但设计师的那个是切图出来,所以你需要在BalloonMarker里面找到open override func draw(context: CGContext, point: CGPoint)这个方法,然后删除或者注释掉那个很长的属性if let color = color{},新建了一个属性叫open var img: UIImage?然后直接在被你删掉或者注释掉的地方添加

if let img = img{
            img.draw(at: CGPoint(x:rect.origin.x-2,y:rect.origin.y))
        }

编译一次后你就可以直接在外部调用 img 这个属性并且赋值了

数据源的坑

如果你需要显示中文,恭喜来这个坑,目前我没找到什么好的解决办法
比如想显示 x 轴的文字,就需要self.chartView.xAxis.valueFormatter = self;并且实现它的数据源代理就可以用他的方法了,- (NSString * _Nonnull)stringForValue:(double)value axis:(ChartAxisBase * _Nullable)axis;目前没有很好的利用这个方法,觉得不好用

代理的坑

charts 主要用到有三个代理

@objc optional func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight)
    
    // Called when nothing has been selected or an "un-select" has been made.
    @objc optional func chartValueNothingSelected(_ chartView: ChartViewBase)
    
    // Callbacks when the chart is moved / translated via drag gesture.
    @objc optional func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat)

第一个是点击后响应的方法,第二个是点击同一个点响应的方法,第三个是拖动就会响应的方法

先说第一个吧,点击后根据你点击的 x 的位置定位到你点击的那个点,并且高亮显示,如果你有用 markerview 就会显示出来,包括高亮线,由于我用不到,所以就设置高亮线为透明

我这边的要求是,不管点击哪个点,都要自动移动到中间,所以需要取出你点击的点,然后移动到中间
[self.chartView moveViewToAnimatedWithXValue:entry.x-3 yValue:0 axis:AxisDependencyLeft duration:.3];

相对来说这个是很容易实现的,毕竟你点击后能够得到对于的ChartDataEntry的值,所以算出你点击的位置然后设置在中间都是很容易的,但是滑动就难了,滑动的时候需要保证每次滑动都是在中间,随便说下,滑动方法里面的 dx 和 dy 是在你每次手指滑动的时候重新开始计算的,拿 dx 来说,你滑动左边数值是从0开始加上去的,右边滑动是从0到负数的叠加,还好 chartview 有两个属性可以用来做判断,分别是
self.chartView.highestVisibleX
self.chartView.lowestVisibleX
NSInteger index = self.chartView.highestVisibleX-3;拿到 index
然后显示高亮状态[self.chartView highlightValueWithX:index y:model.realWeight dataSetIndex:0 callDelegate:NO];
但是这样还是不行,毕竟滑动不会自动居中,所以就导致显示的点可能会不在中间,所以自己改源码增加了一个滑动结束的代理

高亮的坑

安卓那边高亮显示可以直接用
- (void)highlightValueWithX:(double)x dataSetIndex:(NSInteger)dataSetIndex;
但是 iOS 这边直接用这个方法是不行的,在 github 上也有人问过此类问题,最后只能用另一个属性方法
- (void)highlightValueWithX:(double)x y:(double)y dataSetIndex:(NSInteger)dataSetIndex;
但由于我在滑动代理里面增加了高亮的方法,高亮方法默认是打开代理的状态,这样会导致滑动出错,所以需要用另一个高亮显示的方法
- (void)highlightValueWithX:(double)x y:(double)y dataSetIndex:(NSInteger)dataSetIndex callDelegate:(BOOL)callDelegate;
设置代理为 NO 就可以了

总结

其实到了最后跟设计图还是有很多不同的,放出实际图,其实多少辛酸泪不是简单两语能够说得清道得明,排版有点乱,大家请将就下哈~这是第一版,希望以后再努力的去改一改吧!

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 姜米迦 潘丽媛是我老家最好的闺蜜。我们放假了一起玩,一起吃饭,一起写作业...... ...
    花信儿阅读 368评论 0 0
  • 尘烟。如梦。 光阴溅落尘埃,翻飞着些许温凉如烟的旧梦,划过我迷离的眼, 惊悸的眼眸里,错错落落,斑斑驳驳的印记,流...
    糖糖婉儿阅读 382评论 1 2
  • 第6章 请求帮助 在表达观察、感受和需要之后,我们请求他人的帮助。以什么样的方式提出请求容易得到积极回应呢? 提出...
    郭云若阅读 842评论 0 0
  • 小钱同学说:爸,妈,春节到我这里给我烧饭吃吧,我想吃春卷呢。于是各种审批,携程、booking、Airbnb各...
    南海杭州阅读 637评论 4 4