iOS-高效設置圓角

一韩脑、前因

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 屬性來繪制圓角:

  1. UIView的contents無內(nèi)容可以直接通過設置cornerRadius達到效果辑鲤。
  2. 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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末流强,一起剝皮案震驚了整個濱河市痹届,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌打月,老刑警劉巖队腐,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奏篙,居然都是意外死亡柴淘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門秘通,熙熙樓的掌柜王于貴愁眉苦臉地迎上來为严,“玉大人,你說我怎么就攤上這事肺稀〉诠桑” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵话原,是天一觀的道長夕吻。 經(jīng)常有香客問我,道長稿静,這世上最難降的妖魔是什么梭冠? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮改备,結(jié)果婚禮上控漠,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好盐捷,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布偶翅。 她就那樣靜靜地躺著,像睡著了一般碉渡。 火紅的嫁衣襯著肌膚如雪聚谁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天滞诺,我揣著相機與錄音形导,去河邊找鬼。 笑死习霹,一個胖子當著我的面吹牛朵耕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淋叶,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阎曹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了煞檩?” 一聲冷哼從身側(cè)響起处嫌,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斟湃,沒想到半個月后熏迹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡桐早,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年癣缅,在試婚紗的時候發(fā)現(xiàn)自己被綠了厨剪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哄酝。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祷膳,靈堂內(nèi)的尸體忽然破棺而出陶衅,到底是詐尸還是另有隱情,我是刑警寧澤直晨,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布搀军,位于F島的核電站,受9級特大地震影響勇皇,放射性物質(zhì)發(fā)生泄漏罩句。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一敛摘、第九天 我趴在偏房一處隱蔽的房頂上張望门烂。 院中可真熱鬧,春花似錦、人聲如沸屯远。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慨丐。三九已至坡脐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間房揭,已是汗流浹背备闲。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捅暴,地道東北人浅役。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像伶唯,于是被迫代替她去往敵國和親觉既。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動畫效果乳幸,實現(xiàn)這些動畫的過程并不復雜瞪讼,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,489評論 6 30
  • 轉(zhuǎn)載:http://www.reibang.com/p/32fcadd12108 每個UIView有一個伙伴稱為l...
    F麥子閱讀 6,200評論 0 13
  • 每個UIView有一個伙伴稱為layer粹断,一個CALayer符欠。UIView實際上并沒有把自己畫到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,107評論 0 17
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜瓶埋,今天將帶大家一窺iOS動畫全貌希柿。在這里你可以看...
    F麥子閱讀 5,111評論 5 13
  • 一個無論多么強大的武林高手都會存在“死穴”,所謂的“死穴”也就是致命的弱點养筒,有的在身體上曾撤,有的在心里上,還有的在思...
    夏野閱讀 233評論 0 0