前言
在一个
UITextField 或者 UITextView中长按,屏幕就会出现一个辅助编辑的工具条。这个工具条的轮廓是一个带三角形箭头的圆角矩形,看起来就像是一个“气泡”样式的提示框,这是一种非常常见的形状效果。UIKit里的UIPopoverPresentationController可以让一个Controller以这种形式展示。但是如果要对View进行操作,似乎就没有比较直接的方法,有一种解决方法是对其layer的mask属性进行设置。方法一: 用图片mask
要做出这种效果,有一种速成方法。首先做一张下面这样的png图片,然后把这张图片设置为一个
CALayer对象的contents,再用这个CALayer去mask目标View的layer。代码示例:
UIImage *img = [UIImage imageNamed:@"Bubble.png"]; CALayer *maskLayer = [CALayer layer]; maskLayer.frame = myView.bounds; maskLayer.contents = (__bridge id _Nullable)(img.CGImage); myView.layer.mask = maskLayer; myView.layer.backgroundColor = [UIColor purpleColor].CGColor;
很显然,这种实现方法可定制性不高,如果需要把三角形箭头的位置居中,或者把它放在矩形框的左方,又或者需要修改一下圆角的弧度......面对这些要求,可能需要做更多的
png图片。方法二: 用“笔”画出来
仔细看一下这个形状,它无非就是几条直线加上一些圆角,事实上完全可以用“笔”把它画出来。
前期准备
在开始绘制图形之前,我们需要先定位一些关键的“点”。如下图所示,灰色虚线外框是需要
mask的目标视图的大小,绿色区域是最终要绘制的图形,而红色点的坐标在绘制过程中需要使用。假定我们按照顺时针方向绘制这个图形,并且先画“箭头”,再圆角矩形框。那么我们先把这些红点按照顺时针方向编号,并且暂时把它们放进数组里
points中。除此之外,我们还要理解
void CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)这个函数。前四个参数用来指定两个跟绘制有关的坐标点,最后一个参数用来指定圆角的半径(如果这个参数为0,则不会有圆角效果)。除了这五个参数以外,这个函数还会受一个坐标点影响,就是绘画的那支“笔”当前所在的坐标。举个例子,先看下面这张图:
pointtA和pointB指定了函数的前四个参数,radius就是最后一个参数,“画笔”当前所在的坐标点就是Current point,那么上述函数根据这些参数绘制出来的曲线就是黑色的实线。动笔
有了上面这些准备之后,终于可以动笔了。
// 获取上下文 UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); CGContextRef ctx = UIGraphicsGetCurrentContext(); // 因为第一步是要画箭头处的那个“0”的地方 // 所以把“笔”放在“0”在顺时针方向顺序的上一个点: “6” CGPoint currentPoint = [[points objectAtIndex:6] CGPointValue]; CGContextMoveToPoint(ctx, currentPoint.x, currentPoint.y); CGPoint pointA, pointB; CGFloat radius; int i = 0; while(i<7) { // 整个过程需要7次循环 // 箭头处(0,1,2三个点)是三个尖角,矩形框是四个圆角 radius = i < 3 ? 0 : 10; // radius = i < 3 ? 4 : 10; // 全画成圆角 pointA = [[points objectAtIndex:i] CGPointValue]; // 画矩形框最后一个圆角的时候,pointB就是points[0] pointB = [[points objectAtIndex:(i+1)%7] CGPointValue]; CGContextAddArcToPoint(ctx, pointA.x, pointA.y, pointB.x, pointB.y, radius); i = i + 1; } // 获取path CGContextClosePath(ctx); CGPathRef path = CGContextCopyPath(ctx); UIGraphicsEndImageContext(); // 生成layer CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.path = path; // 设置目标view的layer的mask属性 myView.layer.mask = maskLayer;
代码封装
由于我觉得平时可能经常会用到这种形状效果,所以我把它封装起来了。这样每次需要做这种效果的时候,只需根据
View的size去调用接口就可以了。示例:
BubbleLayer *bbLayer = [[BubbleLayer alloc]initWithSize:myView.bounds.size]; // 提供的一些自定义设置 // bubbleLayer.arrowDirection = ArrowDirectionTop; // bubbleLayer.arrowHeight = 12; // bubbleLayer.arrowWidth = 18; // bubbleLayer.arrowPosition = 0.3; [myView.layer setMask:[bbLayer layer]];
封装的工作就是提供一些个性化的参数设置,比如“箭头”的高度、宽度、方向以及相对位置,还有矩形框的圆角半径这些。进一步的,还有根据这些个性化的参数和
View的size,去计算上面说到的7个关键点。这些工作都比较简单,所以我就不再赘述了,完整代码(包括Swift版)我放在了GitHub仓库。