目录
摘要
拿来即用短时间效率虽然挺高的,但是拿来的东西没有消化一次,就无法得心应手的使用它。
这次的探索思路就是,查询官方文档,设置不同的值测试单个方法中参数的变化,之后测试两个方法的执行顺序,处理的思路,最后思考总结。
在总结方法的处理逻辑时,使用伪代码的方式梳理方法的执行思路。避免解释文本太多,增加理解的成本。
最近在学习小程序开发,接触到 flex 方式布局,很喜欢这种快速和方便的方式。所以当遇到一个页面上居中显示文本的需求的时候,就想直接在 uilabel 上处理,然后在uilabel上设置它的内边距(类似 flex 布局)。而不是先放一个 view。然后在这个view 上放置一个 uilabel 控件,通过设置 uilabel 控件距离父 view 的距离实现。
先看代码实现,下面的代码,是搜索之后的解决方式,如果只是拿去使用,直接复制到项目中即可。需要在设置text前设置textinsets
class shlabel: uilabel { var textinsets: uiedgeinsets = .zero override func drawtext(in rect: cgrect) { super.drawtext(in: rect.inset(by: textinsets)) } override func textrect(forbounds bounds: cgrect, limitedtonumberoflines numberoflines: int) -> cgrect { let insets = textinsets var rect = super.textrect(forbounds: bounds.inset(by: insets), limitedtonumberoflines: numberoflines) rect.origin.x -= insets.left rect.origin.y -= insets.top rect.size.width = (insets.left insets.right) rect.size.height = (insets.top insets.bottom) return rect } }
为什么这种方式可以实现内边距?
接下来是梳理一下,为什么这样实现。首先查看开发者文档,看代码块中这两个方法是做什么的
函数 | drawtext(in rect: cgrect) | textrect(forbounds bounds:, limitedtonumberoflines numberoflines) -> cgrect |
---|---|---|
标题 | 在rect的区域中绘制文本或者阴影 | 返回文本的绘制的 rect 区域 |
详细 | 如果需要修改 label 中的绘图行为,需要重写这个方法。这个方法已经配置用于绘图的默认环境和文本颜色,在重写的方法中,可以自定义绘制方法,然后调用super或者自己进行绘图。 | 在系统执行其他文本计算之前重写这个方法(这个太难理解),如果调用 sizetofit()和sizethatfits(_:)会触发这个方法 |
链接 | https://developer.apple.com/documentation/uikit/uilabel/1620527-drawtext | https://developer.apple.com/documentation/uikit/uilabel/1620545-textrect |
之后验证这两个方法的执行顺序,和各自的作用时,发现当 uilabel 的 text 赋值时,会首先调用textrect方法,之后drawtext方法被调用。
textrect在当文本rect的实际宽度大于设置uilabel的实际宽度时,会再次被调用,当然drawtext也是在textrect两次调用之后被调用。
textrect 的作用
看到这里,似乎可以理解开发者文档中提到的在系统执行其他文本计算之前重写这个方法了。这个方法的作用就是先获取 uilabel 的 bounds 和 text 的行数,通过调用 super 方法计算出 text 的 rect 区域,返回给系统。
经过多次测试验证发现执行逻辑(伪代码):
// frame 是设置 uilabel 时的 frame if numberoflines == 1 { textrect 被调用 return retc 的 width = text 的 widht } else { if text 文本的 width < frame 的 width { text rect 被调用 return retc 的 width = text 的 widht } else { text rect 被调用两次后 以 frame 的 wdith 为限制,计算出 text 的 height return rect 的 size = (frame 的 width,text 的 height) } }
drawtext 的作用
看drawtext中的rect参数,就是textrect方法返回的rect。text文本的实际绘制区域就通过重写drawtext方法,并在其中调用它的super方法实现。
经过多次验证,这里的rect并不完全是textrect方法中返回的rect。它们之间的关系是(伪代码):
// frame 是设置 uilabel 的 frame // rect 是 `textrect` 返回的 dx = frame.x dy = frame.y if frame.width 确定不变 { dwidth = frame.width } else { dwidth = rect.width } if frame.height 确定不变 { dheight = frame.height } else { dheight = rect.width } return drawtext 中的 rect(dx,dy,dwidth,dheight)
再问:为什么用这种方式实现内边距?
耐心看完这两个方法之后,对题目中的问题,多少有些思路了。那么就理顺一下这个思路。
首先确定一个共识,就是设置uilabel的内边距,是确定uilabel的frame区域里面,调整text的显示区域。有了这个共识,接下来就好办了。
- 第一步就要用textrect方法获取到text的显示区域,默认text的显示区域和uilabel的bounds区域是一样的
- 那就需要和咱们自己设置的内边距值计算获取到新的text文本的rect区域。
- 最后就用drawtext方法重新绘制一下text的rect区域显示。
那么为什么要用这种方式实现呢?因为目前只有这两个方法和 text 文本直接有关系。
优化
理论搞了这么多,到了输出一些干货的时候了。
如果,uilabel的frame已经确定了,重要的是width和height确定了。那么textrect方法就可以不用重写。
class shlabel: uilabel { var textinsets: uiedgeinsets = .zero override func drawtext(in rect: cgrect) { super.drawtext(in: rect.inset(by: textinsets)) } }
这里就可以看出,当uilabel的height不确定时,重写textrect来帮忙确定uilabel的高度。经过验证下来,这个方法中的 x 和 y 也是不用处理的,什么时候会用到它?我目前还没有遇到。
class shlabel: uilabel { var textinsets: uiedgeinsets = .zero override func drawtext(in rect: cgrect) { super.drawtext(in: rect.inset(by: textinsets)) } override func textrect(forbounds bounds: cgrect, limitedtonumberoflines numberoflines: int) -> cgrect { let insets = textinsets var rect = super.textrect(forbounds: bounds.inset(by: insets), limitedtonumberoflines: numberoflines) // rect.origin.x -= insets.left // rect.origin.y -= insets.top rect.size.width = (insets.left insets.right) rect.size.height = (insets.top insets.bottom) return rect } }
扩展
文章到这里,就结束了。如果你是一个细节控,感觉textrect在需要还是不需要的时候都被调用。drawtext方法,不管设置还是不设置内边距也总是被调用,会不会影响性能啊?
这里提供两个方法解决:
- 可操作性的,就是尽量考虑需求,在不得不用的时候再使用
- 心理安慰性质的,那就是放下。细想一下,这两个方法都是重写的方法,重写的本质是什么?那就是不执行自己的方法,执行重写的方法。换句话说,就算系统不走重写的方法,也要走自己的方法。而这些代码对性能的影响,不值一提。
新发现
突然之间,有没有发现,咱们似乎也明白了,为什么uilabel不用固定它的height,它就可以自己确定高度,完全展示 text文本?你想.你细想...