iOS 高效添加圓角效果 性能優(yōu)化 實(shí)戰(zhàn)講解

轉(zhuǎn)載自:iOS 高效添加圓角效果 性能優(yōu)化 實(shí)戰(zhàn)講解

圓角(RounderCorner)是一種很常見的視圖效果,相比于直角,它更加柔和優(yōu)美,易于接受。但很多人并不清楚如何設(shè)置圓角的正確方式和原理街望。設(shè)置圓角會帶來一定的性能損耗,如何提高性能是另一個需要重點(diǎn)討論的話題弟跑。我查閱了一些現(xiàn)有的資料灾前,收獲良多的同時也發(fā)現(xiàn)了一些誤導(dǎo)人錯誤。本文總結(jié)整理了一些知識點(diǎn)孟辑,概括如下:

  • 設(shè)置圓角的正確姿勢及其原理
  • 設(shè)置圓角的性能損耗
  • 其他設(shè)置圓角的方法豫柬,以及最優(yōu)選擇

我為本文制作了一個 demo,讀者可以在我的 github 上 clone 下來:CornerRadius扑浸,如果覺得有幫助還望給個star以示支持烧给。項(xiàng)目由 Swift 實(shí)現(xiàn),但請務(wù)必相信我即使你只會 Objective-C喝噪,也可以看懂它础嫡。因?yàn)槠渲械年P(guān)鍵知識與 Swift 無關(guān)。

我為本文制作了一個 demo酝惧,讀者可以在我的 github 上 clone 下來:CornerRadius榴鼎,如果覺得有幫助還望給個star以示支持。項(xiàng)目由 Swift 實(shí)現(xiàn)晚唇,但請務(wù)必相信我即使你只會 Objective-C巫财,也可以看懂它。因?yàn)槠渲械年P(guān)鍵知識與 Swift 無關(guān)哩陕。

正確姿勢

首先平项,我想要聲明的一點(diǎn)是:設(shè)置圓角很簡單赫舒,它不會帶來任何性能損耗。

因?yàn)檫@件事本來就很簡單闽瓢,它只需要一行代碼:

view.layer.cornerRadius = 5

先別急著關(guān)掉網(wǎng)頁接癌,也別急著回復(fù),我們讓事實(shí)說話扣讼。打開 Instuments缺猛,選擇 Core Animation 調(diào)試,你會發(fā)現(xiàn)既沒有 Off-Screen Render椭符,也沒有降低幀數(shù)荔燎。關(guān)于使用 Instuments 分析應(yīng)用,你可以參考我的這篇文章:UIKit性能調(diào)優(yōu)實(shí)戰(zhàn)講解销钝。從截圖中可以看到第三個棕色視圖確確實(shí)實(shí)設(shè)置了圓角:

1171077-ce706c8797fdcdef.jpg

不過查看一下代碼可以發(fā)現(xiàn)有咨,有一個 UILabel 也設(shè)置了圓角,但是沒有表現(xiàn)出任何變化曙搬。關(guān)于這一點(diǎn),你可以查看 cornerRadius 屬性的注釋:

By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

也就是說在默認(rèn)情況下鸽嫂,這個屬性只會影響視圖的背景顏色和 border纵装。對于 UILabel 這樣內(nèi)部還有子視圖的控件就無能為力了。所以很多情況下我們會看到這樣的代碼:

label.layer.cornerRadius = 5
label.layer.masksToBounds = true

我們把第二行代碼添加到 CustomTableViewCell 的構(gòu)造方法中据某,再次運(yùn)行 Instument橡娄,就可以看到圓角效果了。

性能損耗

如果你勾選上 Color Offscreen-Rendered Yellow癣籽,就會發(fā)現(xiàn) label 的四周出現(xiàn)了黃色的標(biāo)記挽唉,說明這里出現(xiàn)了離屏渲染。關(guān)于離屏渲染的介紹筷狼,同樣可以參考:UIKit性能調(diào)優(yōu)實(shí)戰(zhàn)講解瓶籽,就不在本文贅述了。

需要強(qiáng)調(diào)的一點(diǎn)是埂材,離屏渲染并非由設(shè)置圓角導(dǎo)致的塑顺!通過控制變量的方法很容易得出這個結(jié)論,因?yàn)?UIView 只是設(shè)置了 cornerRadius俏险,但它沒有出現(xiàn)離屏渲染严拒。某些比較權(quán)威的文章,比如 StackoverflowCodeReview 都提到設(shè)置 cornerRadius 會導(dǎo)致離屏渲染從而影響性能竖独,我想這實(shí)在是冤枉了可愛的 cornerRadius 變量裤唠,也誤導(dǎo)了別人。

雖然設(shè)置 masksToBounds 會導(dǎo)致離屏渲染莹痢,從而影響性能种蘸,但是這個影響到底會有多大墓赴?在我的 iPhone6 上,即使出現(xiàn)了 17 個帶有圓角的視圖劈彪,滑動時的幀數(shù)依然在 58 - 59 fps 左右波動竣蹦。

然而,這并非說明 iOS 9 做了什么特殊優(yōu)化沧奴,或者是離屏渲染的影響不大痘括,其主要原因在于圓角不夠多。當(dāng)我將一個 UIImageView 也設(shè)置成圓角滔吠,也就是屏幕上的圓角視圖達(dá)到 34 個時纲菌,fps 大幅度下降,大約只有 33 左右疮绷『采啵基本上已經(jīng)達(dá)到了影響用戶體驗(yàn)的范圍。因此冬骚,一切不講依據(jù)的優(yōu)化都是耍流氓椅贱,如果你的圓角視圖不多,cell 不復(fù)雜只冻,就不要費(fèi)力氣折騰了庇麦。

高效地設(shè)置圓角

假設(shè)現(xiàn)在圓角視圖非常多(比如在 UICollectionView 中),那么如何為視圖高效的添加圓角呢喜德?網(wǎng)上的教程大多沒有說全山橄,因?yàn)檫@個事要分兩種情況考慮。為普通的 UIView 設(shè)置圓角舍悯,和為 UIImageView 設(shè)置圓角的原理截然不同航棱。

有一種做法是這樣的,這種寫法試圖實(shí)現(xiàn) cornerRadius = 3 的效果:

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
}

不過這是一種錯的離譜的寫法萌衬!

首先饮醇,我們應(yīng)該盡量避免重寫 drawRect 方法。不恰當(dāng)?shù)氖褂眠@個方法會導(dǎo)致內(nèi)存暴增秕豫。舉個例子驳阎,iPhone6 上與屏幕等大的 UIView,即使重寫一個空的 drawRect 方法馁蒂,它也至少占用 750 * 1134 * 4 字節(jié) ≈ 3.4 Mb 的內(nèi)存呵晚。在內(nèi)存惡鬼drawRect 及其后續(xù)中,作者詳細(xì)介紹了其中原理沫屡,據(jù)他測試饵隙,在 iPhone6 上空的、與屏幕等大的視圖重寫 drawRect 方法會消耗 5.2 Mb 內(nèi)存沮脖〗鹈總之芯急,能避免重寫 drawRect 方法就盡可能避免。

其次驶俊,這種方法本質(zhì)上是用遮罩層 mask 來實(shí)現(xiàn)娶耍,因此同樣無可避免的會導(dǎo)致離屏渲染。我試著將此前 34 個視圖的圓角改用這種方法實(shí)現(xiàn)饼酿,結(jié)果 fps 掉到 11 左右榕酒。已經(jīng)屬于卡出翔的節(jié)奏了。

忘掉這種寫法吧故俐,下面介紹正確的高效設(shè)置圓角的姿勢想鹰。

為 UIView 添加圓角

這種做法的原理是手動畫出圓角。雖然我們之前說過药版,為普通的視圖直接設(shè)置 cornerRadius 屬性即可辑舷。但萬一不可避免的需要使用 masksToBounds,就可以使用下面這種方法槽片,它的核心代碼如下:

func kt_drawRectWithRoundedCorner(radius radius: CGFloat,
                                    borderWidth: CGFloat,
                                backgroundColor: UIColor,
                                    borderColor: UIColor) -> UIImage {
    
    
    UIGraphicsBeginImageContextWithOptions(sizeToFit, false, UIScreen.mainScreen().scale)
    let context = UIGraphicsGetCurrentContext()
    CGContextMoveToPoint(context, 開始位置);  // 開始坐標(biāo)右邊開始
    CGContextAddArcToPoint(context, x1, y1, x2, y2, radius);  // 這種類型的代碼重復(fù)四次`
    
    CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
    let output = UIGraphicsGetImageFromCurrentImageContext();`
    UIGraphicsEndImageContext();`
    
    return output
}

這個方法返回的是 UIImage何缓,也就是說我們利用 Core Graphics 自己畫出了一個圓角矩形。除了一些必要的代碼外还栓,最核心的就是 CGContextAddArcToPoint 函數(shù)碌廓。它中間的四個參數(shù)表示曲線的起點(diǎn)和終點(diǎn)坐標(biāo),最后一個參數(shù)表示半徑蝙云。調(diào)用了四次函數(shù)后氓皱,就可以畫出圓角矩形路召。最后再從當(dāng)前的繪圖上下文中獲取圖片并返回勃刨。

有了這個圖片后,我們創(chuàng)建一個 UIImageView 并插入到視圖層級的底部:

extension UIView {
   func kt_addCorner(radius radius: CGFloat,
   borderWidth: CGFloat,
   backgroundColor: UIColor,
   borderColor: UIColor) {

  let imageView = UIImageView(image: kt_drawRectWithRoundedCorner(radius: radius,

   borderWidth: borderWidth,
   backgroundColor: backgroundColor,
   borderColor: borderColor))
   self.insertSubview(imageView, atIndex: 0)
  }
}

完整的代碼可以在項(xiàng)目中找到股淡,使用時身隐,你只需要這樣寫:

let view = UIView(frame: CGRectMake(1,2,3,4))

view.kt_addCorner(radius: 6)
為 UIImageView 添加圓角

相比于上面一種實(shí)現(xiàn)方法,為 UIImageView 添加圓角更為常用唯灵。它的實(shí)現(xiàn)思路是直接截取圖片:

extension UIImage {
    
    func kt_drawRectWithRoundedCorner(radius radius: CGFloat, _ sizetoFit: CGSize) -> UIImage {
        
        let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit)
        
        UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
        
        CGContextAddPath(UIGraphicsGetCurrentContext(),
                         UIBezierPath(roundedRect: rect,
                                      byRoundingCorners: UIRectCorner.AllCorners,
                                      cornerRadii: CGSize(width: radius,
                                                          height: radius)).CGPath)
        CGContextClip(UIGraphicsGetCurrentContext())
        
        self.drawInRect(rect)
        CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
        
        let output = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        return output
        
    }
}

圓角路徑直接用貝塞爾曲線繪制贾铝,一個意外的 bonus 是還可以選擇哪幾個角有圓角效果。這個函數(shù)的效果是將原來的 UIImage 剪裁出圓角埠帕。配合著這函數(shù)垢揩,我們可以為 UIImageView 拓展一個設(shè)置圓角的方法:

extension UIImageView {

/**
 !!!只有當(dāng) imageView 不為nil 時,調(diào)用此方法才有效果
:param: radius 圓角半徑
*/
override func kt_addCorner(radius radius: CGFloat) {

 self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius, self.bounds.size)

   }
}

完整的代碼可以在項(xiàng)目中找到敛瓷,使用時叁巨,你只需要這樣寫:

let imageView = let imgView1 = UIImageView(image: UIImage(name: ""))
imageView.kt_addCorner(radius: 6)

提醒:

無論使用上面哪種方法,你都需要小心使用背景顏色呐籽。因?yàn)榇藭r我們沒有設(shè)置 masksToBounds锋勺,因此超出圓角的部分依然會被顯示蚀瘸。因此,你不應(yīng)該再使用背景顏色庶橱,可以在繪制圓角矩形時設(shè)置填充顏色來達(dá)到類似效果贮勃。

在為 UIImageView 添加圓角時,請確保 image 屬性不是 nil苏章,否則這個設(shè)置將會無效寂嘉。

實(shí)戰(zhàn)測試

回到 demo 中,測試一下剛剛定義的這兩個設(shè)置圓角的方法布近。首先在 setupContent 方法中把這兩行代碼的注釋取消掉:

imgView1.kt_addCorner(radius: 5)

imgView2.kt_addCorner(radius: 5)

然后使用自定義的方法為 label 和 view 設(shè)置圓角:

view.kt_addCorner(radius: 6)
label.kt_addCorner(radius: 6)

現(xiàn)在垫释,我們不僅成功的添加了圓角效果,同時還保證了性能不受影響:

1171077-331ca6074d5b02c2.jpeg

性能測試

總結(jié)
  • 如果能夠只用 cornerRadius 解決問題撑瞧,就不用優(yōu)化棵譬。
  • 如果必須設(shè)置 masksToBounds,可以參考圓角視圖的數(shù)量预伺,如果數(shù)量較少(一頁只有幾個)也可以考慮不用優(yōu)化订咸。
  • UIImageView 的圓角通過直接截取圖片實(shí)現(xiàn),其它視圖的圓角可以通過 Core Graphics 畫出圓角矩形實(shí)現(xiàn)酬诀。
參考資料

轉(zhuǎn)載自 http://www.cocoachina.com/ios/20160301/15486.html 支持原創(chuàng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脏嚷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瞒御,更是在濱河造成了極大的恐慌父叙,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肴裙,死亡現(xiàn)場離奇詭異趾唱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蜻懦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門甜癞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宛乃,你說我怎么就攤上這事悠咱。” “怎么了征炼?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵析既,是天一觀的道長。 經(jīng)常有香客問我谆奥,道長眼坏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任雄右,我火速辦了婚禮空骚,結(jié)果婚禮上纺讲,老公的妹妹穿的比我還像新娘。我一直安慰自己囤屹,他們只是感情好熬甚,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肋坚,像睡著了一般乡括。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上智厌,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天诲泌,我揣著相機(jī)與錄音,去河邊找鬼铣鹏。 笑死敷扫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诚卸。 我是一名探鬼主播葵第,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼合溺!你這毒婦竟也來了卒密?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤棠赛,失蹤者是張志新(化名)和其女友劉穎哮奇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睛约,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鼎俘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了痰腮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片而芥。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡律罢,死狀恐怖膀值,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情误辑,我是刑警寧澤沧踏,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站巾钉,受9級特大地震影響翘狱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜砰苍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一潦匈、第九天 我趴在偏房一處隱蔽的房頂上張望阱高。 院中可真熱鬧,春花似錦茬缩、人聲如沸赤惊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽未舟。三九已至,卻和暖如春掂为,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工喷屋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苏研,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓欲诺,卻偏偏與公主長得像野揪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瞧栗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

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