一韩脑、前因
CALayer由背景色backgroundColor、內(nèi)容contents见坑、邊緣borderWidth&borderColor構(gòu)成
- 設置圓角不就是設置layer的cornerRadius嗎嚷掠,還談什么高效?
因為這個屬性只會影響視圖的背景顏色和 border荞驴。所以該方法只對UIView有效不皆,對于 UIImageView 這樣內(nèi)部還有子視圖的控件就無能為力了。 - 所以很多情況下我們會加上layer.masksToBounds的設置熊楼。
這樣圓角效果就有了霹娄。但是,如果你勾選上 Color Offscreen-Rendered Yellow鲫骗,就會發(fā)現(xiàn) label 的四周出現(xiàn)了黃色的標記犬耻,說明這里出現(xiàn)了離屏渲染。關(guān)于離屏渲染的介紹执泰,可以參考:UIKit性能調(diào)優(yōu)實戰(zhàn)講解香追。
之前有的文章說 iOS 9 做了什么特殊優(yōu)化,或者是離屏渲染的影響不大坦胶,其主要原因在于圓角不夠多透典。當我將一個 UIImageView 也設置成圓角晴楔,也就是屏幕上的圓角視圖達到 34 個時,fps 大幅度下降峭咒,大約只有 33 左右税弃。基本上已經(jīng)達到了影響用戶體驗的范圍凑队。因此则果,一切不講依據(jù)的優(yōu)化都是耍流氓,如果你的圓角視圖不多漩氨,cell 不復雜西壮,就不要費力氣折騰了。
二叫惊、首先款青,來個錯誤示范:
override func drawRect(rect: CGRect) {
let maskPath = UIBezierPath(roundedRect: rect,
byRoundingCorners: .AllCorners,
cornerRadii: CGSize(width: 3, height: 3))
let maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = maskPath.CGPath
self.layer.mask = maskLayer
}
- 首先,我們應該盡量避免重寫 drawRect
方法霍狰。不恰當?shù)氖褂眠@個方法會導致內(nèi)存暴增抡草。舉個例子,iPhone6 上與屏幕等大的 UIView
蔗坯,即使重寫一個空的 drawRect
方法康震,它也至少占用 750 * 1134 * 4 字節(jié) ≈ 3.4 Mb
的內(nèi)存。在 內(nèi)存惡鬼drawRect 及其后續(xù)中宾濒,作者詳細介紹了其中原理腿短,據(jù)他測試,在 iPhone6 上空的绘梦、與屏幕等大的視圖重寫 drawRect
方法會消耗 5.2 Mb 內(nèi)存答姥。總之谚咬,能避免重寫 drawRect
方法就盡可能避免鹦付。 - 其次,這種方法本質(zhì)上是用遮罩層 mask
來實現(xiàn)择卦,因此同樣無可避免的會導致離屏渲染敲长。我試著將此前 34 個視圖的圓角改用這種方法實現(xiàn),結(jié)果 fps 掉到 11 左右秉继。已經(jīng)屬于卡出翔的節(jié)奏了祈噪。
三、實戰(zhàn):設置圓角的正確姿勢
1.UIView設置圓角
對于 contents 無內(nèi)容或者內(nèi)容的背景透明(無涉及到圓角以外的區(qū)域)的layer尚辑,直接設置layer的 backgroundColor 和 cornerRadius 屬性來繪制圓角:
- UIView的contents無內(nèi)容可以直接通過設置cornerRadius達到效果辑鲤。
- UILable的contents也一樣,所以也可通過設置cornerRadius達到效果杠茬。不過label不能直接設置backgroundColor月褥,因為這樣設置的是contents的backgroundColor弛随,需要設置layer. backgroundColor。
前面提到過UIView通過cornerRadius就可以宁赤,但是如果特殊情況需要設置layer.masksToBounds舀透,就不要通過cornerRadius方式了,會用到如下方式:
@implementation UIView (RounderCorner)
- (void)dlj_addRounderCornerWithRadius:(CGFloat)radius size:(CGSize)size
{
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef cxt = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(cxt, [UIColor redColor].CGColor);
CGContextSetStrokeColorWithColor(cxt, [UIColor redColor].CGColor);
CGContextMoveToPoint(cxt, size.width, size.height-radius);
CGContextAddArcToPoint(cxt, size.width, size.height, size.width-radius, size.height, radius);//右下角
CGContextAddArcToPoint(cxt, 0, size.height, 0, size.height-radius, radius);//左下角
CGContextAddArcToPoint(cxt, 0, 0, radius, 0, radius);//左上角
CGContextAddArcToPoint(cxt, size.width, 0, size.width, radius, radius);//右上角
CGContextClosePath(cxt);
CGContextDrawPath(cxt, kCGPathFillStroke);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[imageView setImage:image];
[self insertSubview:imageView atIndex:0];
}
這個方法返回的是 UIImage决左,也就是說我們利用 Core Graphics 自己畫出了一個圓角矩形愕够。除了一些必要的代碼外,最核心的就是 CGContextAddArcToPoint 函數(shù)佛猛。它中間的四個參數(shù)表示曲線的起點和終點坐標惑芭,最后一個參數(shù)表示半徑。調(diào)用了四次函數(shù)后继找,就可以畫出圓角矩形遂跟。最后再從當前的繪圖上下文中獲取圖片并返回。
有了這個圖片后码荔,我們創(chuàng)建一個 UIImageView 并插入到視圖層級的底部漩勤。
使用時感挥,你只需要這樣寫:
[view dlj_addRounderCornerWithRadius:10 size:CGSizeMake(60, 30)];
我這里只是單純?yōu)榱藢崿F(xiàn)圓角缩搅,當然大家在用的時候可以添加背景顏色、以及設置邊框的屬性触幼。
2.ImageView添加圓角
相比于上面一種實現(xiàn)方法硼瓣,為 UIImageView 添加圓角更為常用。它的實現(xiàn)思路是直接截取圖片:
@implementation UIImage (ImageRoundedCorner)
- (UIImage*)imageAddCornerWithRadius:(CGFloat)radius andSize:(CGSize)size{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
CGContextAddPath(ctx,path.CGPath);
CGContextClip(ctx);
[self drawInRect:rect];
CGContextDrawPath(ctx, kCGPathFillStroke);
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
圓角路徑直接用貝塞爾曲線繪制置谦,一個意外的 bonus 是還可以選擇哪幾個角有圓角效果堂鲤。這個函數(shù)的效果是將原來的 UIImage 剪裁出圓角。配合著這函數(shù)媒峡,我們可以為 UIImageView 拓展一個設置圓角的方法來更加方便的使用瘟栖。
提醒
- 無論使用上面哪種方法,你都需要小心使用背景顏色谅阿。因為此時我們沒有設置 masksToBounds半哟,因此超出圓角的部分依然會被顯示。因此签餐,你不應該再使用背景顏色寓涨,可以在繪制圓角矩形時設置填充顏色來達到類似效果。
- 在為 UIImageView 添加圓角時氯檐,請確保 image 屬性不是 nil戒良,否則這個設置將會無效。
四冠摄、擴展:其他會導致離屏渲染的解決方案
以下離屏渲染操作糯崎,按對性能影響等級從高到低進行排序:
1. shadows(陰影)
方案:在設置完layer的shadow屬性之后几缭,設置layer.shadowPath = [UIBezierPath pathWithCGRect:view.bounds].CGPath;
2.圓角(前邊已解決過)
3.mask遮罩
方案:不用mask(哈哈)
4. allowsGroupOpacity(組不透明)
開啟CALayer的 allowsGroupOpacity 屬性后,子 layer 在視覺上的透明度的上限是其父 layer 的 opacity (對應UIView的 alpha )拇颅,并且從 iOS 7 以后默認全局開啟了這個功能奏司,這樣做是為了讓子視圖與其容器視圖保持同樣的透明度。
方案:關(guān)閉 allowsGroupOpacity 屬性樟插,按產(chǎn)品需求自己控制layer透明度韵洋。
5. edge antialiasing(抗鋸齒)
方案:不設置 allowsEdgeAntialiasing 屬性為YES(默認為NO)
6. shouldRasterize(光柵化)
當視圖內(nèi)容是靜態(tài)不變時,設置 shouldRasterize(光柵化)為YES黄锤,此方案最為實用方便搪缨。
view.layer.shouldRasterize = true;
view.layer.rasterizationScale = view.layer.contentsScale;
但當視圖內(nèi)容是動態(tài)變化(如后臺下載圖片完畢后切換到主線程設置)時,使用此方案反而為增加系統(tǒng)負荷鸵熟。
7.Core Graphics API(核心繪圖)
Core Graphics API(核心繪圖)的繪制操作會導致CPU的離屏渲染副编。
方案:放到后臺線程中進行。
參考資料:
iOS 高效添加圓角效果實戰(zhàn)講解
iOS 離屏渲染優(yōu)化(Offscreen Render)