之前实现了一个尺子,效果如图:
考虑到以后其他地方可能还会用到,就做成了一个pod库,放到了github上,传送门
后来使用过程中发现并修改了很多问题,来总结一下优化历程:
最开始是用drawRect的方式实现的,就是在drawRect方法中,把每一条刻度都画出来,核心方法如下:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
[self.rulerLineColor set];
CGFloat startPoint = self.rulerMargin;
for (int i = 0; i <= (self.maxValue - self.minValue); i++) {
CGContextSetLineWidth(context, 1);
startPoint = i * self.rulerSpacing + self.rulerMargin;
CGFloat endPoint = 0;
if (i % 5 == 0) {
endPoint = self.longLineDistance;
} else {
endPoint = self.shortLineDistance;
}
CGContextMoveToPoint(context, startPoint, 0);
CGContextAddLineToPoint(context, startPoint, endPoint);
CGContextStrokePath(context);
}
CGContextMoveToPoint(context, self.rulerMargin, 0);
CGContextAddLineToPoint(context, startPoint, 0);
CGContextStrokePath(context);
}
(具体代码见tag 1.0.0)
这个方法比较直观,所见即所得嘛,很容易想到,但很快就发现了一个大问题:
太占内存了!!!
如果只有100条刻度还好,占用内存也就几M,但实际场景是这个是用来选择身高的,精度要到0.1,所以刻度数量是原来的10倍。。。
那内存就吃不消了,一个小控件要几十M,那来两三个内存不就炸了。。。
至于为什么drawRect会这么站内存,可以参考客:
内存恶鬼drawRect
内存恶鬼drawRect(续:答疑篇)
所以就迎来了第一次主要的优化:
使用CALayer来绘图,弃用drawRect方式;
思路跟原来没有什么大的变化,只是不再使用drawRect的方法,改为在initWithFrame方法中,直接添加layer,核心方法如下:
- (void)setupUI {
CGFloat startPoint = self.rulerMargin;
for (int i = 0; i <= (self.maxValue - self.minValue) / self.accuracy; i++) {
CALayer *subLayer = [CALayer layer];
startPoint = i * self.rulerSpacing + self.rulerMargin;
CGFloat endPoint = 0;
if (i % 10 == 0) {
endPoint = self.longLineDistance;
}
else if (i % 5 == 0) {
endPoint = (self.longLineDistance + self.shortLineDistance) / 2.0;
}
else {
endPoint = self.shortLineDistance;
}
subLayer.frame = CGRectMake(startPoint, 0, 1, endPoint);
subLayer.backgroundColor = self.rulerLineColor.CGColor;
[self.layer addSublayer:subLayer];
}
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(self.rulerMargin, 0, startPoint - self.rulerMargin, 1);
layer.backgroundColor = self.rulerLineColor.CGColor;
[self.layer addSublayer:layer];
}
(具体代码见tag 1.3.1)
这个就是采用了上面提到的两篇博客提到的优化方式,效果也确实很明显,内存占用很小很小了;
然而计划赶不上变化,产品经理提出了新的需求,要在一个界面上显示多个尺子,而且可以随时隐藏或者显示;
所以就又碰到了一个大问题:
绘制太慢,会卡住主线程!!!
修改UI的操作只能在主线程操作,而要同时画出几个尺子,每个尺子都有成百上千个subLayer,这卡了真不能怪手机,只能怪设计不合理。。。(产品经理:怪我咯~)
其实不是产品设计不合理,是UI控件设计不合理,尺子这种大面积重复的界面,肯定是可以复用的啦,怎么能直接画这么长呢!!!
所以就迎来了第二次主要优化:
使用UICollectionView的复用机制重构整个控件
(具体代码见tag 2.1.0)
这个思路就跟之前有比较大的不同了,要把一个完整的尺子拆成N个复用的cell;
思前想后,我最后采用的方式是:不管刻度的间距是多少或者尺子的精度是多少,每个cell固定显示10个刻度;这样就可以比较容易的确定cell的大小和个数。
还有几个细节优化的地方:
- cell里面固定有几个subLayer,是在初始化的时候就生成好的,在cellForRow方法中,只需要修改他们的frame,颜色等属性就可以;
- 原来的markView,是用一个View实现的,当初是为了能用autoLayout,现在直接用个CAShapeLayer实现,手动计算frame来确定位置
最终效果如图:
后续:
理论上,在cellForRow中修改View的frame等属性也是比较消耗性能的操作,在cell初始化的时候,就把各种属性都设置对才是更理想的做法;
目前还没想到有什么好的方式可以实现,因为cell类跟rulerView的实例联系不起来。。。
同学们有什么好的思路,望不吝赐教~
有什么问题,欢迎讨论~
也可以直接去github(https://github.com/Phelthas/LXMRulerView ) 上提交issue或者pull request