一个自定义UI控件的优化历程

之前实现了一个尺子,效果如图:


考虑到以后其他地方可能还会用到,就做成了一个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的大小和个数。
还有几个细节优化的地方:

  1. cell里面固定有几个subLayer,是在初始化的时候就生成好的,在cellForRow方法中,只需要修改他们的frame,颜色等属性就可以;
  2. 原来的markView,是用一个View实现的,当初是为了能用autoLayout,现在直接用个CAShapeLayer实现,手动计算frame来确定位置

最终效果如图:

rulerView2.gif

后续:

理论上,在cellForRow中修改View的frame等属性也是比较消耗性能的操作,在cell初始化的时候,就把各种属性都设置对才是更理想的做法;
目前还没想到有什么好的方式可以实现,因为cell类跟rulerView的实例联系不起来。。。
同学们有什么好的思路,望不吝赐教~

有什么问题,欢迎讨论~
也可以直接去github(https://github.com/Phelthas/LXMRulerView ) 上提交issue或者pull request

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 其实当时也不懂为何我那么得空,坐在那儿两个小时,只为了画这些圈圈...... 圈圈+线条=莫名奇妙的画作........
    雪樱思薰XYSX阅读 1,801评论 3 6
  • 岁月静好,老爸的脑门倍儿亮
    昙花舞阅读 1,166评论 0 0
  • 见过花落,回望花开,花开易见却未见,花落难寻空留痕。青春里的爱恋总是需要珍惜和争取,不然等到花开的时候却始终等不到...
    Renzo阅读 3,359评论 0 0

友情链接更多精彩内容