/** 边框位置 */
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;
复制代码
这个方法很长,由于确实要求复杂:git
来张图解释一下效果:github
这时仿的QQ的弹框效果。bash
各个参数的意思注释里应该很清楚了。下面是实现代码:函数
-(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个边一步一步的加,看一个边的状况就能够了解了:spa
//上边
//从左上角开始
[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个点:箭头的左下角、顶点和右上角。这样走了一下折线。code
若是有圆角就再绘制圆角。cdn
边框就是一个CAShapeLayer,没什么可说的,有个问题是它使用了和mask一样的path,这样它的线条有一半是在mask以外,会被切掉,因此lineWidth作了一个乘2的处理:border.lineWidth = borderWidth*2;
。对象
最后[self removeFCBorder];
和[self markFCBorder:border];
是为了标记加入的边框layer,保证它的惟一,不然这个方法多调用几回就会有多个边框叠加,特别是frame发生改变后,就会出现奇怪的边框了。blog
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