/** 邊框位置 */
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