/** 边框位置 */
typedef NS_ENUM(NSInteger, FCBorderPosition) {
FCBorderPositionTop = 1 << 0,
FCBorderPositionLeft = 1 << 1,
FCBorderPositionBottom = 1 << 2,
FCBorderPositionRight = 1 << 3,
FCBorderPositionAll = FCBorderPositionTop | FCBorderPositionLeft | FCBorderPositionBottom | FCBorderPositionRight,
};
/**
给view添加一个带箭头的边框
@param direction 箭头朝向
@param offset 箭头的坐标,如果是在左右朝向,传箭头中心位置的y值;如果是上下朝向,传箭头中心位置x值
@param width 箭头的宽度
@param height 箭头的高度
@param cornerRadius 圆角半径,<=0不设圆角
@param borderWidth 边框宽度
@param borderColor 边框颜色
*/
- (void)addArrowBorderAt:(FCBorderPosition)direction
offset:(CGFloat)offset
width:(CGFloat)width
height:(CGFloat)height
cornerRadius:(CGFloat)cornerRadius
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor;
这个方法很长,因为确实要求复杂:
- 要箭头
- 要圆角
- 还要有边框
来张图解释一下效果:

这时仿的QQ的弹框效果。
各个参数的意思注释里应该很清楚了。下面是实现代码:
-(void)addArrowBorderAt:(FCBorderPosition)direction offset:(CGFloat)offset width:(CGFloat)width height:(CGFloat)height cornerRadius:(CGFloat)cornerRadius borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor{
[self removeFCBorder];
//只有一个mask层
CAShapeLayer *mask = [[CAShapeLayer alloc] init];
mask.frame = self.bounds;
mask.name = FCBorderMaskName;
self.layer.mask = mask;
UIBezierPath *path = [[UIBezierPath alloc] init];
CGFloat minX = 0, minY = 0, maxX = self.bounds.size.width, maxY = self.bounds.size.height;
if (direction == FCBorderPositionTop) {
minY = height;
}else if (direction == FCBorderPositionRight){
maxX -= height;
}else if (direction == FCBorderPositionLeft){
minX += height;
}else if (direction == FCBorderPositionBottom){
maxY -= height;
}
//上边
[path moveToPoint:CGPointMake(minX+cornerRadius, minY)];
if (direction == FCBorderPositionTop) {
[path addLineToPoint:CGPointMake(offset-width/2, minY)];
[path addLineToPoint:CGPointMake(offset, minY-height)];
[path addLineToPoint:CGPointMake(offset+width/2, minY)];
}
[path addLineToPoint:CGPointMake(maxX-cornerRadius, minY)];
//右上角
if (cornerRadius>0) {
[path addArcWithCenter:CGPointMake(maxX-cornerRadius, minY+cornerRadius) radius:cornerRadius startAngle:-M_PI_2 endAngle:0 clockwise:YES];
}
//右边
if (direction == FCBorderPositionRight) {
[path addLineToPoint:CGPointMake(maxX, offset-width/2)];
[path addLineToPoint:CGPointMake(maxX+height, offset)];
[path addLineToPoint:CGPointMake(maxX, offset+width/2)];
}
[path addLineToPoint:CGPointMake(maxX, maxY-cornerRadius)];
//右下角
if (cornerRadius>0) {
[path addArcWithCenter:CGPointMake(maxX-cornerRadius, maxY-cornerRadius) radius:cornerRadius startAngle:0 endAngle:M_PI_2 clockwise:YES];
}
//下边
if (direction == FCBorderPositionBottom) {
[path addLineToPoint:CGPointMake(offset-width/2, maxY)];
[path addLineToPoint:CGPointMake(offset, maxY+height)];
[path addLineToPoint:CGPointMake(offset+width/2, maxY)];
}
[path addLineToPoint:CGPointMake(minX+cornerRadius, maxY)];
//左下角
if (cornerRadius>0) {
[path addArcWithCenter:CGPointMake(minX+cornerRadius, maxY-cornerRadius) radius:cornerRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
}
//右边
if (direction == FCBorderPositionLeft) {
[path addLineToPoint:CGPointMake(minX, offset-width/2)];
[path addLineToPoint:CGPointMake(minX-height, offset)];
[path addLineToPoint:CGPointMake(minX, offset+width/2)];
}
[path addLineToPoint:CGPointMake(minX, minY+cornerRadius)];
//右下角
if (cornerRadius>0) {
[path addArcWithCenter:CGPointMake(minX+cornerRadius, minY+cornerRadius) radius:cornerRadius startAngle:M_PI endAngle:M_PI_2*3 clockwise:YES];
}
mask.path = [path CGPath];
if (borderWidth>0) {
CAShapeLayer *border = [[CAShapeLayer alloc] init];
border.path = [path CGPath];
border.strokeColor = borderColor.CGColor;
border.lineWidth = borderWidth*2;
border.fillColor = [UIColor clearColor].CGColor;
[self.layer addSublayer:border];
[self markFCBorder:border];
}
}
首先说思路:layer的mask可以把不需要的地方遮住,所以绘制一个特定的mask给layer就可以把圆角和箭头切割出来。然后使用一个另外一个layer,它的图形跟mask一样,但是用来绘制边框,把这个layer加上去边框就有了。
大部分的工作都是在绘制mask的路径,分为4个边一步一步的加,看一个边的情况就可以了解了:
//上边
//从左上角开始
[path moveToPoint:CGPointMake(minX+cornerRadius, minY)];
if (direction == FCBorderPositionTop) {
//多增加3个点,路线饶了一下
[path addLineToPoint:CGPointMake(offset-width/2, minY)];
[path addLineToPoint:CGPointMake(offset, minY-height)];
[path addLineToPoint:CGPointMake(offset+width/2, minY)];
}
//到达右上角
[path addLineToPoint:CGPointMake(maxX-cornerRadius, minY)];
//右上角
if (cornerRadius>0) {
[path addArcWithCenter:CGPointMake(maxX-cornerRadius, minY+cornerRadius) radius:cornerRadius startAngle:-M_PI_2 endAngle:0 clockwise:YES];
}
整个过程就是从左上角画线,画到右上角。 如果箭头在上边,则在上边加入箭头,加入箭头其实就是多加3个点:箭头的左下角、顶点和右上角。这样走了一下折线。
如果有圆角就再绘制圆角。
边框就是一个CAShapeLayer,没什么可说的,有个问题是它使用了和mask同样的path,这样它的线条有一半是在mask之外,会被切掉,所以lineWidth做了一个乘2的处理:border.lineWidth = borderWidth*2;
。
最后[self removeFCBorder];
和[self markFCBorder:border];
是为了标记加入的边框layer,保证它的唯一,否则这个方法多调用几次就会有多个边框叠加,特别是frame发生改变后,就会出现奇怪的边框了。
static NSString *FCBorderLayerKey = @"FCBorderLayerKey";
static NSString *FCBorderMaskName = @"FCBorderMaskName";
-(void)markFCBorder:(CALayer *)layer{
objc_setAssociatedObject(self, &FCBorderLayerKey, layer, OBJC_ASSOCIATION_RETAIN);
}
-(void)removeFCBorder{
if ([self.layer.mask.name isEqualToString:FCBorderMaskName]) {
self.layer.mask = nil;
}
CAShapeLayer *oldLayer = objc_getAssociatedObject(self, &FCBorderLayerKey);
if (oldLayer) [oldLayer removeFromSuperlayer];
}
这里使用了runtime的关联对象函数做了联系。
另:写了一个弹框的工具类,欢迎查看FCPopActionView