0x00

这是我在公司做项目时遇到的一个需求,就是实现类似下图的效果

右侧气泡的切图是这样的:

就是要实现这个切图的拉伸,达到动图中的效果,气泡的尖角始终对准时间线上的小圆点。(ps:具备像素眼的童鞋可能发现这几个气泡的高度不是等差的,这是因为我在气泡的下面还加了一个透明的view,气泡的边线与该view的边线间有一定的间隙,label显示的是tableViewCell的整体高度)


0x01

关于iOS中图片的拉伸,我看的小码哥写的这篇文章,图片拉伸的中文资料相对来说要少一些,而且为数不多的文章里内容也大多是重复的。
小码哥的文章里介绍了端盖的概念,其实我不是很理解,写代码试了试之后大概有一点概念,但是某些情况我还是不知道是什么原因造成的,这个到后面再说。
我选择使用- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode这个方法,对应下来代码如下:

1
_bubbleImage.image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height * 0.7f, image.size.width * 0.3f, image.size.height * 0.3f, image.size.width * 0.7f) resizingMode:UIImageResizingModeStretch];

这样就可以实现动图中的效果,看上去很简单是吗?
首先我们要明白一个概念,该方法中设置的UIEdgeInsets是图片不用被拉伸的区域,我这个需求的情况属于比较简单的,因为要拉伸的图片背景色纯白,不用考虑拉伸模式的问题,如果要考虑拉伸模式,那UIEdgeInsets就不能设置的这么随意了。
虽然切图中气泡的尖角在正中间,但该需求中气泡的尖角始终在气泡图片的上半部分。则上半部分不需要拉伸的部分应该更多,具体多少取决于图片的情况,这个切图中上半部分的60%就可以,下半部分就可以取10~40%不拉伸,但不能取的过于低(比如低于10%),因为这个图片的底部是有圆角的。
这个切图我上半部分取了70%,下半部分取了30%,加起来正好是100%,水平方向也是一样。这样看似是图片水平和垂直方向100%的内容都不进行拉伸,但实际还是拉伸了,可以看看官方文档里的说明:

If resizable areas have a width or height of 1 pixel—that is, a horizontally resizable area is 1 pixel wide, a vertically resizable area is 1 pixel tall, or the center region of the image is 1 x 1 pixel—iOS draws the image by stretching the 1-pixel region. This mode provides the fastest performance (for nonzero cap insets).

可拉伸区域不可能取到0 x 0,至少是1 x 1,而且1 x 1的时候渲染速度最快,所以我选择了这些数值。

水平方向的取值道理和垂直方向一样,只要左半部分尺寸不要太低以致气泡尖角被拉伸就可以。


0x02

现在让我们来更深入的研究一下。
当我设置上半部分55%和下半部分55%不拉伸的时候,即UIEdgeInsetsMake(image.size.height * 0.55f, image.size.width * 0.3f, image.size.height * 0.55f, image.size.width * 0.7f),会出现这样的情况:

我本以为这样会和上下各50%不拉伸产生一样的情况,恰好尖角被拉伸:

现在我猜测当UIEdgeInsets有重叠部分的时候,top的优先级高于bottom,同理left的优先级高于right,产生红圈标记出的奇怪现象的可能就是从上边界往下数55%的位置正好是尖角的下部分,由于UIEdgeInsets区域重叠,在垂直方向还是只拉伸一个像素,就把尖角部分的那一个像素给拉伸了。
但是我没有找到官方文档中相应的部分可以佐证我的想法。
我觉得这一切都是因为官方文档里的那句话:

for nonzero cap insets(因为没有0 x 0的可拉伸区域)