App中圓角常用的場景有:UIImageView(頭像及cell中圖片)、Button按鈕(按鈕)、Label文字(文字)等等。這些控件的圓角設(shè)置原理都是一樣的,這里就以UIImageView圓角設(shè)置為例。
1.系統(tǒng)提供的圓角設(shè)置屬性:
icon.layer.cornerRadius = 50
icon.layer.masksToBounds = true
cornerRadius屬性本身并沒有什么問題,而masksToBounds屬性會(huì)造成離屏渲染從而影響性能。
ps:Offscreen rendering(離屏渲染):離屏渲染指的是在圖像在繪制到當(dāng)前屏幕前,需要先進(jìn)行一次渲染,之后才繪制到當(dāng)前屏幕。在第一次渲染時(shí),GPU(Core Animation)或CPU(Core Graphics)需要額外的一塊內(nèi)存來進(jìn)行渲染,完成后再繪制到屏幕。offscreen到onscreen需要進(jìn)行上下文切換,這個(gè)切換的性能消耗是昂貴的。
事實(shí)上,當(dāng)界面中單個(gè)或少量控件用此方法設(shè)置圓角,并不會(huì)影響性能,我們并不需要擔(dān)心,但如果界面中大量使用此方法設(shè)置圓角,比如TableView中顯示圓角圖片等,滾動(dòng)時(shí)就會(huì)出現(xiàn)界面卡頓,這當(dāng)然不是我們想要看到的。
2.為了不讓圖像離屏渲染:
可以自己繪制圓角:
@objc private func testDrawCornerImage(){
let startTime = CACurrentMediaTime()
for _ in 0..<100 {
let icon = UIImageView.init(frame: CGRect.init(x: 100, y: 400, width: 100, height: 100))
icon.image = UIImage.init(named: "ic_icon")
//1.建立上下文
UIGraphicsBeginImageContextWithOptions(icon.bounds.size, true, 0)
//獲取當(dāng)前上下文
let ctx = UIGraphicsGetCurrentContext()
//設(shè)置填充顏色
UIColor.white.setFill()
UIRectFill(icon.bounds)
//2.添加圓及裁切
let rect = CGRect.init(x: 0, y: 0, width: icon.bounds.size.width, height: icon.bounds.size.height)
ctx?.addEllipse(in: rect)
//裁切
ctx?.clip()
//3.繪制圖像
icon.draw(icon.bounds)
//4.獲取繪制的圖像
icon.image = UIGraphicsGetImageFromCurrentImageContext()
//5關(guān)閉上下文
UIGraphicsEndImageContext()
view.addSubview(icon)
}
print("方法三:testDrawCornerImage-->圓角設(shè)置消耗時(shí)間\(CACurrentMediaTime() - startTime)")
}
//也可以直接用貝塞爾設(shè)置圓形路徑進(jìn)行繪制
@objc private func testDrawWithBezierPath(){
let startTime = CACurrentMediaTime()
for _ in 0..<100 {
let icon = UIImageView.init(frame: CGRect.init(x: 100, y: 250, width: 100, height: 100))
icon.image = UIImage.init(named: "ic_icon")
//1.建立上下文
UIGraphicsBeginImageContextWithOptions(icon.bounds.size, true, 0)
//設(shè)置填充顏色
UIColor.white.setFill()
UIRectFill(icon.bounds)
//2.創(chuàng)建橢圓path,寬、高一致返回的就是圓形路徑
let path = UIBezierPath.init(ovalIn: icon.bounds)
//也可以這樣:
// let path2 = UIBezierPath.init(roundedRect: icon.bounds, cornerRadius: icon.bounds.size.width*0.5)
//裁切
path.addClip()
//3.繪制圖像
icon.draw(icon.bounds)
//4.獲取繪制的圖像
icon.image = UIGraphicsGetImageFromCurrentImageContext()
//5關(guān)閉上下文
UIGraphicsEndImageContext()
view.addSubview(icon)
}
print("方法二:testDrawWithBezierPath-->圓角設(shè)置消耗時(shí)間\(CACurrentMediaTime() - startTime)")
}
對(duì)上面兩個(gè)繪制圓角的方法性能進(jìn)行了簡單的測試,分別進(jìn)行多輪的100次測試,時(shí)間消耗上差別基本無幾,大家可以選擇任何一種方法使用。
ps: UIGraphicsBeginImageContextWithOptions方法參數(shù)說明:
size —- 圖形上下文的大小
opaque —- 透明開關(guān),如果圖形完全不用透明,設(shè)置為YES以優(yōu)化位圖的存儲(chǔ)。
scale —– 縮放因子,可以用[UIScreen mainScreen].scale來獲取,但實(shí)際上設(shè)為0后,系統(tǒng)就會(huì)自動(dòng)設(shè)置正確的比例了。在Retina屏幕上最好不要自己手動(dòng)設(shè)置他的縮放比例,直接設(shè)置0,系統(tǒng)就會(huì)自動(dòng)進(jìn)行最佳的縮放
關(guān)鍵在于:繪制圓角最好在子線程進(jìn)行,這樣不會(huì)阻塞主線程,用戶體驗(yàn)上會(huì)更好,特別是對(duì)于UITableView列表這樣的場景,異步繪制是必須的,不然UITableView滑動(dòng)可能會(huì)出現(xiàn)卡頓的情況。
可以將設(shè)置圓角方法放在UIImageView+Extension.swift(Swift在3.0以后沒有Category,Extension文件之分了)文件中,創(chuàng)建setCornerImage方法:
import Foundation
import UIKit
extension UIImageView{
func setCornerImage(){
//異步繪制圖像
DispatchQueue.global().async(execute: {
//1.建立上下文
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0)
//獲取當(dāng)前上下文
let ctx = UIGraphicsGetCurrentContext()
//設(shè)置填充顏色
UIColor.white.setFill()
UIRectFill(self.bounds)
//2.添加圓及裁切
ctx?.addEllipse(in: self.bounds)
//裁切
ctx?.clip()
//3.繪制圖像
self.draw(self.bounds)
//4.獲取繪制的圖像
let image = UIGraphicsGetImageFromCurrentImageContext()
//5關(guān)閉上下文
UIGraphicsEndImageContext()
DispatchQueue.main.async(execute: {
self.image = image
})
})
}}
在需要設(shè)置圓角的地方直接調(diào)用即可,例如:
let icon = UIImageView.init(frame: CGRect.init(x: 10, y: 10,
width: 50, height: 50))
icon.image = UIImage.init(named: "ic_icon")
icon.setCornerImage()
addSubview(icon)