前言
在一个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
仓库。
网友评论