一. Quartz2D
Quartz 2D 是一個二維繪圖引擎,它能夠支持:
1、繪制圖形: 線條、三角形、矩形、圓和弧等;
2、繪制文字;
3、繪制,生成圖片(圖像);
4、讀取,生成PDF;
5、截圖,裁剪圖片;
6、自定義 UI 控件(普通的 UI 控件無法使用 UIkit 框架實現(xiàn),可以使用 Quartz 2D 技術(shù)將控件內(nèi)部的結(jié)構(gòu)畫出來,自定義控件的樣子,例如五角星??)。
幾個概念:
1、圖像上下文(Graphics Context) - 相當(dāng)于一個畫筆
- Graphics Context是一個數(shù)據(jù)類型(CGContextRef),封裝了Quartz繪制圖像到輸出設(shè)備的信息,輸出設(shè)備可以是PDF文件、Bitmap(位圖文件,一種圖形文件)或者顯示器的窗口上
2)Quartz中所有的對象都是繪制到一個Graphics Context中
3)當(dāng)用Quartz繪圖時,所有設(shè)備相關(guān)的特性都包含在Graphics Context中,換句話說,我們可以簡單地給Quartz繪圖序列指定不同的Graphics Context,就可將相同的圖像繪制在不同的設(shè)備上,而不需要任何設(shè)備相關(guān)的計算,這些都有Quartz替我們完成
2、Quartz2D坐標(biāo)系
1)Quartz中默認(rèn)的坐標(biāo)系統(tǒng)是:原定(0,0)在左下角,沿著X軸從左到右坐標(biāo)值逐漸增大,沿著Y軸從下到上左鍵增加
2)有些技術(shù)在設(shè)置他們的graphics Context時使用了不同于Quartz的默認(rèn)坐標(biāo)系統(tǒng),最常見的是系統(tǒng)原點修改為左上角
3)坐標(biāo)系的轉(zhuǎn)換
//相對原點旋轉(zhuǎn)上下文坐標(biāo)系
CGContextRotateCTM(CGContextRef c, CGFloat angle)
//相對原點平移上下文坐標(biāo)系
CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
//縮放上下文坐標(biāo)系
CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)
3、Quartz2D的繪圖順序
1)誰后繪制誰顯示在頂部,即疊加到最上面
2)利用Quartz2D繪制UIView
當(dāng)在UIView子類中重寫drawRect方法時,iOS會自動準(zhǔn)備好一個圖像上下文,可以通過調(diào)用UIGraphicsGetCurrentContext()來獲取
3)只要一個UIView需要被刷新或者重繪,drawRect方法就會被調(diào)用,需要注意的是:重繪時應(yīng)該調(diào)用setneedsDisplay,而不能直接調(diào)用drawRect,setNeedsDisplay會自動調(diào)用drawRect:
- drawRect注意事項
drawRect:是在UIViewController的loadView和viewDidLoad兩方法之后調(diào)用的
drawRect:如果試圖沒有設(shè)置frame,將導(dǎo)致該方法不能執(zhí)行
如果設(shè)置UIView的contentMode屬性值為UIViewContentModeRedraw,那么將在每次更改frame時自動調(diào)用drawRect:
如果使用UIView繪圖,只能在drawRect:方法中獲取相應(yīng)的CGContextRef并繪圖,而在其他方法中獲取的CGContextRef不能用于繪圖
4、Quartz內(nèi)存管理
1)使用含有"Create"或"Copy"的函數(shù)創(chuàng)建的對象,使用完后必須釋放,否則將導(dǎo)致內(nèi)存泄露,使用不含有"Create"或"Copy"的函數(shù)獲取的對象,則不需要釋放
2)如果retain了一個對象,不再使用時需要將其release掉,可以使用Quratz2D的函數(shù)來指定retain和release一個對象,例如創(chuàng)建了一個CGColorSpace對象,則使用函數(shù)CGColorSpaceRetain和CGColorSpaceRelease來retain和release對象,也可以使用CoreFoundation的CFRetain和CGRelease。注意不能傳遞NULL值給這些函數(shù)
5、Quartz2D繪圖的基礎(chǔ)元素-路徑
路徑定義了一條或者多條形狀或子路徑
子路徑可以包含一條或者多條直線或曲線
子路徑也可以是一些簡單的形狀,例如線、圓形、矩形或者星型等
子路徑還可以包含復(fù)雜的形狀,例如地圖輪廓或者涂鴉等
路徑是可以是開放的,也可以是封閉的,對于封閉路徑可以是空心的也可以是實心的
二. drawRect
有時候我們需要自己實現(xiàn)圖像繪制,需要重寫drawRect方法。
首先我們需要了解一下layoutSubviews。layoutSubviews在以下情況下會被調(diào)用:
1、init初始化不會觸發(fā)layoutSubviews。
2、addSubview會觸發(fā)layoutSubviews。
3、設(shè)置view的Frame會觸發(fā)layoutSubviews,當(dāng)然前提是frame的值設(shè)置前后發(fā)生了變化。
4、滾動一個UIScrollView會觸發(fā)layoutSubviews。
5、旋轉(zhuǎn)Screen會觸發(fā)父UIView上的layoutSubviews事件。
6、改變一個UIView大小的時候也會觸發(fā)父UIView上的layoutSubviews事件。
7、直接調(diào)用setLayoutSubviews。
drawRect在以下情況下會被調(diào)用:
1、如果在UIView初始化時沒有設(shè)置rect大小,將直接導(dǎo)致drawRect不被自動調(diào)用。drawRect 掉用是在Controller->loadView, Controller->viewDidLoad 兩方法之后掉用的.所以不用擔(dān)心在 控制器中,這些View的drawRect就開始畫了.這樣可以在控制器中設(shè)置一些值給View(如果這些View draw的時候需要用到某些變量 值)。
2、該方法在調(diào)用sizeToFit后被調(diào)用,所以可以先調(diào)用sizeToFit計算出size。然后系統(tǒng)自動調(diào)用drawRect:方法。
3、通過設(shè)置contentMode屬性值為UIViewContentModeRedraw。那么將在每次設(shè)置或更改frame的時候自動調(diào)用drawRect:。
4、直接調(diào)用setNeedsDisplay,或者setNeedsDisplayInRect:觸發(fā)drawRect:,但是有個前提條件是rect不能為0。
以上1,2推薦;而3,4不提倡
setNeedsDisplay和setNeedsLayout兩個方法都是異步的,setNeedsDisplay會自動調(diào)用drawRect,而setNeedsLayout會自動調(diào)用layoutSubviews。
可以看出layoutSubviews方便數(shù)據(jù)計算,drawRect方便視圖重繪。
另外:
1、若使用UIView繪圖,只能在drawRect:方法中獲取相應(yīng)的contextRef并繪圖。如果在其他方法中獲取將獲取到一個invalidate的ref并且不能用于畫圖。drawRect:方法不能手動顯示調(diào)用,必須通過調(diào)用setNeedsDisplay 或者 setNeedsDisplayInRect ,讓系統(tǒng)自動調(diào)該方法。
2、若使用CALayer繪圖,只能在drawInContext: 中(類似于drawRect)繪制,或者在delegate中的相應(yīng)方法繪制。同樣也是調(diào)用setNeedDisplay等間接調(diào)用以上方法。
3、若要實時畫圖,不能使用gestureRecognizer,只能使用touchbegan等方法來掉用setNeedsDisplay實時刷新屏幕
在iOS系統(tǒng)中所有顯示的視圖都是從基類UIView繼承而來的,同時UIView負(fù)責(zé)接收用戶交互。但是實際上你所看到的視圖內(nèi)容,包括圖形等,都是由UIView的一個實例圖層屬性來繪制和渲染的,那就是CALayer。
CALayer類的概念與UIView非常類似,它也具有樹形的層級關(guān)系,并且可以包含圖片文本、背景色等。它與UIView最大的不同在于它不能響應(yīng)用戶交互,可以說它根本就不知道響應(yīng)鏈的存在,它的API雖然提供了“某點是否在圖層范圍內(nèi)的方法”,但是它并不具有響應(yīng)的能力。
在每一個UIView實例當(dāng)中,都有一個默認(rèn)的支持圖層,UIView負(fù)責(zé)創(chuàng)建并且管理這個圖層。實際上這個CALayer圖層才是真正用來在屏幕上顯示的,UIView僅僅是對它的一層封裝,實現(xiàn)了CALayer的delegate,提供了處理事件交互的具體功能,還有動畫底層方法的高級API。
可以說CALayer是UIView的內(nèi)部實現(xiàn)細(xì)節(jié)。
屏幕上你所看到的東西,其實都是一張張圖片。而為什么我們能看到CALayer的內(nèi)容呢,是因為CALayer內(nèi)部有一個contents屬性。contents默認(rèn)可以傳一個id類型的對象,但是只有你傳CGImage的時候,它才能夠正常顯示在屏幕上。
contents也被稱為寄宿圖,除了給它賦值CGImage之外,我們也可以直接對它進行繪制,繪制的方法正是這次問題的關(guān)鍵,通過繼承UIView并實現(xiàn)-drawRect:方法即可自定義繪制。-drawRect: 方法沒有默認(rèn)的實現(xiàn),因為對UIView來說,寄宿圖并不是必須的,UIView不關(guān)心繪制的內(nèi)容。如果UIView檢測到-drawRect:方法被調(diào)用了,它就會為視圖分配一個寄宿圖,這個寄宿圖的像素尺寸等于視圖大小乘以contentsScale的值(這個屬性與屏幕分辨率有關(guān),在不同模擬器下呈現(xiàn)的內(nèi)存用量不同也是因為它,用于關(guān)聯(lián)邏輯坐標(biāo)和物理坐標(biāo)。因為iOS中的繪圖系統(tǒng)使用的尺寸單位為Point,而屏幕顯示的單位為Pixel,為什么要這樣做呢?其實就是為了隔離變化:對于繪圖而言,并不關(guān)心如何在屏幕上顯示,這些屬于硬件細(xì)節(jié),也不應(yīng)該關(guān)心,因此框架使用了萬金油方法——抽象,繪圖使用與硬件無關(guān)的Point,系統(tǒng)根據(jù)當(dāng)前屏幕的情況自動將Point轉(zhuǎn)成Pixel,所以不論以后硬件屏幕如何變化,使用Point的繪圖系統(tǒng)以不變應(yīng)萬變)。
This value defines the mapping between the logical coordinate space of the layer (measured in points) and the physical coordinate space (measured in pixels). Higher scale factors indicate that each point in the layer is represented by more than one pixel at render time. For example, if the scale factor is
2.0and the layer’s bounds are 50 x 50 points, the size of the bitmap used to present the layer’s content is 100 x 100 pixels.
-drawRect:方法的背后實際上都是底層的CALayer進行了重繪和保存中間產(chǎn)生的圖片,CALayer的delegate屬性默認(rèn)實現(xiàn)了CALayerDelegate協(xié)議,當(dāng)它需要內(nèi)容信息的時候會調(diào)用協(xié)議中的方法來拿。當(dāng)視圖重繪時,因為它的支持圖層CALayer的代理就是視圖本身,所以支持圖層會請求視圖給它一個寄宿圖來顯示,它此刻會調(diào)用:
- (void)displayLayer:(CALayer *)layer;
如果視圖實現(xiàn)了這個方法,就可以拿到layer來直接設(shè)置contents寄宿圖,如果這個方法沒有實現(xiàn),支持圖層CALayer會嘗試調(diào)用:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
這個方法調(diào)用之前,CALayer創(chuàng)建了一個合適尺寸的空寄宿圖(尺寸由bounds和contentsScale決定)和一個Core Graphics的繪制上下文環(huán)境,為繪制寄宿圖做準(zhǔn)備,它作為ctx參數(shù)傳入。在這一步生成的空寄宿圖內(nèi)存是相當(dāng)巨大的,它就是內(nèi)存問題的關(guān)鍵,一旦你實現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其實就是前者的包裝方法),圖層就創(chuàng)建了一個繪制上下文,這個上下文需要的內(nèi)存可從這個公式得出:圖層寬 x圖層高x4字節(jié),寬高的單位均為像素。比如屏幕大小為:
_myDrawer = [[BHBMyDrawer alloc] initWithFrame:CGRectMake(0, 0, SCREEN_SIZE.width, SCREEN_SIZE.height)];
它在iPhone6s plus機器上的上下文內(nèi)存量就是 1920*1080*4字節(jié),相當(dāng)于8.29MB內(nèi)存,圖層每次重繪的時候都需要重新抹掉內(nèi)存然后重新分配。
所以如果需要大量的畫線,繪制操作我們可以使用CAShapeLayer。
CAShapeLayer繼承自CALayer,可使用CALayer的所有屬性
CAShapeLayer需要和貝塞爾曲線配合使用才有意義。貝塞爾曲線可以為其提供形狀,而單獨使用CAShapeLayer是沒有任何意義的。
-
使用CAShapeLayer與貝塞爾曲線可以實現(xiàn)不在view的DrawRect方法中畫出一些想要的圖形。
?
我們可以對比一下CAShapeLayer和drawRect:
DrawRect:DrawRect屬于CoreGraphic框架,占用CPU,消耗性能大。
CAShapeLayer:CAShapeLayer屬于CoreAnimation框架,通過GPU來渲染圖形,節(jié)省性能。動畫渲染直接提交給手機GPU,不消耗內(nèi)存。
CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類。用CGPath來定義想要繪制的圖形,CAShapeLayer會自動渲染。它可以完美替代我們的直接使用Core Graphics繪制layer,對比之下使用CAShapeLayer有以下優(yōu)點:
- 渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會比用Core Graphics快很多。
- 高效使用內(nèi)存。一個CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個寄宿圖形,所以無論有多大,都不會占用太多的內(nèi)存。
- 不會被圖層邊界剪裁掉。
- 不會出現(xiàn)像素化。
1. UIBezierPath
- (void)moveToPoint:(CGPoint)point; //設(shè)置初始線段的起點+ (instancetype)bezierPathWithRect:(CGRect)rect; //根據(jù)一個矩形畫貝塞爾曲線+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect; //根據(jù)一個矩形畫內(nèi)切曲線。通常用它來畫圓或者橢圓。+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; //該方法是畫矩形,但是這個矩形是可以畫圓角的。第一個參數(shù)是矩形,第二個參數(shù)是圓角大小 + (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii; //該方法功能和上一個是一樣的,但是可以指定某一個角畫成圓角。像這種我們就可以很容易地給UIView擴展添加圓角的方法+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise; /** 這個工廠方法用于畫弧,參數(shù)說明如下: center: 弧線中心點的坐標(biāo) radius: 弧線所在圓的半徑 startAngle: 弧線開始的角度值 endAngle: 弧線結(jié)束的角度值 clockwise: 是否順時針畫弧線 **/
lineCapStyle
屬性是用來設(shè)置線條拐角帽的樣式的,其中有三個選擇:
/* Line cap styles. */
typedef CF_ENUM(int32_t, CGLineCap) {
kCGLineCapButt, 默認(rèn)的
kCGLineCapRound, 輕微圓角
kCGLineCapSquare 第三個正方形
};
lineJoinStyle
屬性是用來設(shè)置兩條線連結(jié)點的樣式,其中也有三個選擇:
/* Line join styles. */
typedef CF_ENUM(int32_t, CGLineJoin) {
kCGLineJoinMiter, 默認(rèn)的表示斜接
kCGLineJoinRound, 圓滑銜接
kCGLineJoinBevel 斜角連接
};
2. 繪制
通常我們需要進行以下的步驟進行繪制。
- 1 獲取當(dāng)前的上下文(這里只能獲取一次,并且只能在drawRect方法中獲?。?/li>
- 2 描述路徑、形狀(就是處理想要顯示的樣子)
- 3 把描述好的路徑、形狀添加早上下文中
- 4 顯示上下文內(nèi)容
//1.畫線條
- (void)drawRect:(CGRect)rect{
//1.獲取上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.描述路徑
UIBezierPath *path = [UIBezierPath bezierPath];
//起點
[path moveToPoint:CGPointMake(0, 0)];
//終點
[path addLineToPoint:CGPointMake(100, 100)];
//設(shè)置顏色
[[UIColor whiteColor] setStroke];
//設(shè)置線寬
CGContextSetLineWidth(contextRef, 5);
//3.添加路徑
CGContextAddPath(contextRef, path.CGPath);
//顯示路徑
CGContextStrokePath(contextRef);
}

//2.畫矩形
- (void)drawRect:(CGRect)rect{
//1.獲取上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.描述路徑
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 100, 100)];
//設(shè)置顏色
[[UIColor greenColor] set];
//3.添加路徑
CGContextAddPath(contextRef, path.CGPath);
//顯示填充路徑
CGContextFillPath(contextRef);
// 方法2:
// //1.獲取上下文
// CGContextRef contextRef = UIGraphicsGetCurrentContext();
// //2.描述路徑
// UIBezierPath *path = [UIBezierPath bezierPath];
// //起點
// [path moveToPoint:CGPointMake(10, 10)];
// //第二個點
// [path addLineToPoint:CGPointMake(100, 10)];
// //第三個點
// [path addLineToPoint:CGPointMake(100, 100)];
// //第四個點
// [path addLineToPoint:CGPointMake(10, 100)];
// //閉合路徑 也等于 [path addLineToPoint:CGPointMake(10, 10)];
// [path closePath];
// //設(shè)置顏色
// [[UIColor greenColor] setStroke];
//// [[UIColor greenColor] setFill];
// //3.添加路徑
// CGContextAddPath(contextRef, path.CGPath);
// //顯示描邊路徑
// CGContextStrokePath(contextRef);
//// CGContextFillPath(contextRef);
}

//3.畫圓(要確定圓心、半徑,以及旋轉(zhuǎn)的角度)
- (void)drawRect:(CGRect)rect{
//1、獲取當(dāng)前上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.描述路徑
//ArcCenter:中心點
//radius:半徑
//startAngle:起始角度
//endAngle:結(jié)束角度
//clockwise:是否逆時針
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width*0.5, self.bounds.size.height*0.5) radius:self.bounds.size.width*0.4 startAngle:0 endAngle:M_PI*2 clockwise:NO];
//3.添加路徑到上下文
CGContextAddPath(contextRef, path.CGPath);
//4.設(shè)置顏色
[[UIColor greenColor] setFill];
CGContextFillPath(contextRef);
// //2.也可以畫橢圓
// //1、獲取當(dāng)前上下文
// CGContextRef contextRef = UIGraphicsGetCurrentContext();
// //2.描述路徑 這是畫橢圓的方法
// UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 10, 100, 100)];
// //3.添加路徑到上下文
// CGContextAddPath(contextRef, path.CGPath);
// //4.設(shè)置顏色
// [[UIColor redColor]setFill];
// //4.顯示上下文
// CGContextFillPath(contextRef);
}

//4.畫文字
- (void)drawRect:(CGRect)rect{
//1.獲取當(dāng)前上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.創(chuàng)建文字
NSString *str = @"人生也像坐火車一樣,過去的景色那樣美,讓你流連不舍,但是你總是需要前進,會離開,然后你告訴自我,沒關(guān)聯(lián),我以后必須還會再來看,可其實,往往你再也不會回去。流逝的時刻,退后的風(fēng)景,邂逅的人,終究是漸行漸遠(yuǎn)。";
//上下文
NSMutableDictionary * dic = [NSMutableDictionary dictionary];
dic[NSForegroundColorAttributeName] = [UIColor whiteColor];
[str drawInRect:rect withAttributes:dic];
CGContextFillPath(contextRef);
}

//畫圖片
- (void)drawRect:(CGRect)rect{
//1.獲取當(dāng)前的上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//2.加載圖片
//這里順便咯嗦一句:使用imageNamed加載圖片是會有緩存的
//我們這里只需要加載一次就夠了,不需要多次加載,所以不應(yīng)該保存這個緩存,使用imageWithContentsOfFile
UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Icon-152.png" ofType:nil]];
//繪制的大小位置
// [image drawInRect:rect];
//從某個點開始繪制
// [image drawAtPoint:CGPointMake(10, 10)];
//繪制一個多大的圖片,并且設(shè)置他的混合模式以及透明度
//Rect:大小位置
//blendModel:混合模式
//alpha:透明度
[image drawInRect:rect blendMode:kCGBlendModeNormal alpha:1];
//從某一點開始繪制圖片,并設(shè)置混合模式以及透明度
//point:開始位置
//blendModel:混合模式
//alpha:透明度
// [image drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeNormal alpha:1];
//添加到上下文
CGContextFillPath(contextRef);
}
3. 圖形上下文
- 1 開啟一個圖形上下文
- 2 繪制圖片
- 3 從當(dāng)前上下文獲取新的圖片
- 4 關(guān)閉上下文
/**
根據(jù)傳入imageName獲取圖形上下文
@param imageName imageName
@return image
*/
+ (nullable UIImage *)tt_drawImageWithImageName:(nullable NSString *)imageName{
//1.獲取圖片
UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:nil]];
//2.開啟圖形上下文
UIGraphicsBeginImageContext(image.size);
//3.繪制到圖形上下文中
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
//4.從上下文中獲取圖片
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//5.關(guān)閉圖形上下文
UIGraphicsEndImageContext();
//返回圖片
return newImage;
}
/**
自定義裁剪
@param view 裁剪的view
@param frame 裁剪區(qū)域
@param block 回調(diào)
*/
+ (void)tt_clipView:(nullable UIView *)view cutFrame:(CGRect)frame block:(void(^_Nullable)(UIImage * _Nullable image,NSData * _Nullable imageData))block{
//1.開啟上下文
UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0);
//2、獲取當(dāng)前的上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
//3、添加裁剪區(qū)域
UIBezierPath * path = [UIBezierPath bezierPathWithRect:frame];
[path addClip];
//4、渲染
[view.layer renderInContext:contextRef];
//5、從上下文中獲取
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//7、關(guān)閉上下文
UIGraphicsEndImageContext();
NSData * data = UIImageJPEGRepresentation(newImage, 1);
block(newImage,data);
}

三. 如何高效添加圓角
我們經(jīng)常會給視圖添加圓角。
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
你肯定知道,這會產(chǎn)生離屏渲染,離屏渲染,指的是GPU在當(dāng)前屏幕緩沖區(qū)以外新開辟一個緩沖區(qū)進行渲染操作。
是的,但是實際上cornerRadius是不會離屏渲染的,只有設(shè)置了masksToBounds才會。我們通常會這兩句代碼搭配使用,因為對于內(nèi)部含有子視圖的控件,確實是需要裁剪。
另外,上面我們也說到了drawRect方法對內(nèi)存的影響,所以我們應(yīng)該盡量避免重寫 drawRect 方法。不恰當(dāng)?shù)氖褂眠@個方法會導(dǎo)致內(nèi)存暴增。舉個例子,iPhone6 上與屏幕等大的 UIView,即使重寫一個空的 drawRect 方法,它也至少占用 750 * 1134 * 4 字節(jié) ≈ 3.4 Mb 的內(nèi)存;而7p會達(dá)到8.29Mb。
我們可以使用之前的2個方法去給uiimage添加圓角。
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
具體做法,可以先給UIImage添加一個分類:
- (UIImage *_Nullable)tt_drawRectWithRoundedCorner:(CGFloat)radius andSize:(CGSize)size{
//1.設(shè)置rect
CGRect rect = CGRectMake(0, 0, size.width, size.height);
//2.開啟上下文
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
//3.獲取當(dāng)前的上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//4.設(shè)置裁剪區(qū)域 UIRectCornerAllCorners 為上下左右 根據(jù)radius初始化一個圓角矩形路徑
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
CGContextAddPath(ctx,path.CGPath);
//5.修改當(dāng)前剪切路徑
CGContextClip(ctx);
//5.繪制到圖形上下文中
[self drawInRect:rect];
CGContextDrawPath(ctx, kCGPathFillStroke);
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
//調(diào)用
imageV.image = [imageV.image tt_drawRectWithRoundedCorner:5 andSize:image6.bounds.size];
如果還是覺得麻煩,還可以給UIImageView添加一個分類,添加上一個方法,然后可以直接調(diào)用設(shè)置圓角。
- (void)tt_addCorner:(CGFloat)raduis{
self.image = [self.image tt_drawRectWithRoundedCorner:raduis andSize:self.bounds.size];
}
//調(diào)用
[imageV tt_addCorner:25];
結(jié)論:
其實,如果能夠只用 cornerRadius 解決問題,就不用優(yōu)化。
如果必須設(shè)置 masksToBounds,可以參考圓角視圖的數(shù)量,如果數(shù)量較少(一頁只有幾個)也可以考慮不用優(yōu)化。
UIImageView 的圓角通過直接截取圖片實現(xiàn),其它視圖的圓角可以通過 Core Graphics 畫出圓角矩形實現(xiàn)。