前言
在一個UITextField
或者 UITextView
中長按,屏幕就會出現(xiàn)一個輔助編輯的工具條馒吴。這個工具條的輪廓是一個帶三角形箭頭的圓角矩形偎漫,看起來就像是一個氣泡樣式的提示框唁影,這是一種非常常見的形狀效果。
UIKit
里的UIPopoverPresentationController
可以讓一個Controller
以這種形式展示谜慌。但是如果要對View
進行操作,似乎就沒有比較直接的方法莺奔,有一種解決方法是對其layer
的mask
屬性進行設(shè)置欣范。
方法一: 用圖片mask
要做出這種效果,有一種速成方法令哟。首先做一張下面這樣的png
圖片恼琼,然后把這張圖片設(shè)置為一個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;
很顯然晴竞,這種實現(xiàn)方法可定制性不高,如果需要把三角形箭頭的位置居中,或者把它放在矩形框的左方狠半,又或者需要修改一下圓角的弧度......面對這些要求噩死,可能需要做更多的png
圖片颤难。
方法二: 用“筆”畫出來
仔細看一下這個形狀,它無非就是幾條直線加上一些圓角已维,事實上完全可以用“筆”把它畫出來乐严。
前期準備
在開始繪制圖形之前,我們需要先定位一些關(guān)鍵的“點”衣摩。如下圖所示昂验,灰色虛線外框是需要mask
的目標視圖的大小,綠色區(qū)域是最終要繪制的圖形艾扮,而紅色點的坐標在繪制過程中需要使用既琴。
假定我們按照順時針方向繪制這個圖形,并且先畫“箭頭”泡嘴,再圓角矩形框甫恩。那么我們先把這些紅點按照順時針方向編號,并且暫時把它們放進數(shù)組里points
中酌予。
除此之外磺箕,我們還要理解void CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)
這個函數(shù)。前四個參數(shù)用來指定兩個跟繪制有關(guān)的坐標點抛虫,最后一個參數(shù)用來指定圓角的半徑(如果這個參數(shù)為0松靡,則不會有圓角效果)。除了這五個參數(shù)以外建椰,這個函數(shù)還會受一個坐標點影響雕欺,就是繪畫的那支“筆”當前所在的坐標。
舉個例子棉姐,先看下面這張圖:
pointtA
和pointB
指定了函數(shù)的前四個參數(shù)屠列,radius
就是最后一個參數(shù),“畫筆”當前所在的坐標點就是Current point
伞矩,那么上述函數(shù)根據(jù)這些參數(shù)繪制出來的曲線就是黑色的實線笛洛。
動筆
有了上面這些準備之后,終于可以動筆了乃坤。
// 獲取上下文
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次循環(huán)
// 箭頭處(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;
// 設(shè)置目標view的layer的mask屬性
myView.layer.mask = maskLayer;
代碼封裝
由于我覺得平時可能經(jīng)常會用到這種形狀效果侥袜,所以我把它封裝起來了蝌诡。這樣每次需要做這種效果的時候,只需根據(jù)View
的size
去調(diào)用接口就可以了枫吧。
示例:
BubbleLayer *bbLayer = [[BubbleLayer alloc]initWithSize:myView.bounds.size];
// 提供的一些自定義設(shè)置
// bubbleLayer.arrowDirection = ArrowDirectionTop;
// bubbleLayer.arrowHeight = 12;
// bubbleLayer.arrowWidth = 18;
// bubbleLayer.arrowPosition = 0.3;
[myView.layer setMask:[bbLayer layer]];
封裝的工作就是提供一些個性化的參數(shù)設(shè)置浦旱,比如“箭頭”的高度、寬度九杂、方向以及相對位置颁湖,還有矩形框的圓角半徑這些宣蠕。進一步的,還有根據(jù)這些個性化的參數(shù)和View
的size
甥捺,去計算上面說到的7個關(guān)鍵點抢蚀。這些工作都比較簡單,所以我就不再贅述了镰禾,完整代碼(包括Swift
版)我放在了Github
倉庫皿曲。