iOS基礎(chǔ)05—-UIView與CALayer的聯(lián)系與區(qū)別
UIView
所有的視圖都是由UIView派生而來皂甘,UIView可以處理觸摸事件佳魔,可以支持Core Graphics繪圖凭迹,可以做仿射變換(旋轉(zhuǎn)或縮放)罚屋,以及簡單的滑動和漸變動畫。
CALayer
CALayer和UIView一樣嗅绸,最大的不同是CALayer不響應(yīng)事件脾猛。每一個UIView都對應(yīng)一個CALayer圖層屬性(但是也可以添加無數(shù)個子圖層)。實際上CALayer才是真正用來在屏幕上顯示和做動畫的鱼鸠,UIView只是對它的一個封裝猛拴,提供了一些處理觸摸的功能以及Core Animation底層方法的高級接口。
這樣分離功能的原因就是因為OSX上的NSView是用鼠標(biāo)處理的蚀狰,這樣iOS和MacOS就可以共享CALayer的代碼了愉昆。
CALayer最常用的地方就是在content上放置寄宿圖:
contents: 是Any類型,但是實際上只有CGImage類型才能起效果麻蹋;它常用的處理contents的屬性包括:
contentRect:顯示圖片的某一部分跛溉;
contentGravity:類似于UIImage的contentMode
contentScale:圖片的縮放尺寸;
maskToBounds:是否顯示超出layer范圍的內(nèi)容
contentsCenter:設(shè)置可拉伸區(qū)域
cornerRadius:設(shè)置圓角
borderWidth:邊框?qū)挾?br>
borderColor:邊框顏色
shadowRadius:陰影圓角
shadowOpacity:陰影透明數(shù)
shadowOffset:陰影size
shadowColor:陰影color
shadowPath:使用CGPath對圖層指定陰影形狀扮授,可以提高性能
blueLayer = CALayer()
blueLayer.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
blueLayer.backgroundColor = UIColor.blue.cgColor
layerView.layer.addSublayer(blueLayer)
let img = UIImage(named: "BROOK")
blueLayer.contents = img?.cgImage //基本是UIImage類型芳室,其他類型沒有效果
blueLayer.contentsRect = CGRect.init(x: 0, y: 0, width: 0.5, height: 0.5) //顯示圖的某一部分,按坐標(biāo)來的糙箍,1的話就是顯示全部
blueLayer.contentsGravity = kCAGravityCenter //類似于UIImage的contentMode
blueLayer.contentsScale = (img?.scale)! //按圖片大小縮放
blueLayer.masksToBounds = true // 是否顯示超出Layer.frame范圍的內(nèi)容
blueLayer.contentsCenter = CGRect.init(x: 0.25, y: 0.25, width: 0.5, height: 0.5) //它定義了一個圖片可拉伸的區(qū)域
Custome Drawing:如果不使用圖片的話渤愁,還可以直接用Core Graphics直接在contents繪制寄宿圖,通過繼承UIView的drawRect(這個方法容易造成CPU和內(nèi)存的浪費深夯,莫要輕易使用)方法進(jìn)行繪制抖格,當(dāng)視圖在屏幕上出現(xiàn)時,drawRect會被調(diào)用咕晋;手動調(diào)用setNeedDisplay方法雹拄,drawRect也會被調(diào)用。不過drawRect通常在UIView中使用掌呜,所以如果要在controller上使用滓玖,就可以直接實現(xiàn)CALayerDelegate。
extension CALayerTestController:CALayerDelegate{
//事先會先調(diào)用draw(_ layer: CALayer)质蕉,如果沒有實現(xiàn)就會找draw(_ layer: CALayer, in ctx: CGContext)函數(shù)
func draw(_ layer: CALayer, in ctx: CGContext) {
//使用Core Graphics畫圖,這里會返回圖層的Context給你势篡,直接用就好
ctx.setLineWidth(10)
ctx.setStrokeColor(UIColor.red.cgColor)
ctx.strokeEllipse(in: layer.bounds)
}
}
CALayer雖然不關(guān)心任何響應(yīng)事件,但是它有一系列方法幫你處理事件:contains和hitTest;
contains可以判斷一個點是否在layer的frame里面模暗;
hitTest則可以獲取到點擊事件所在圖層:
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
let point = touches.first?.location(in: self.view)
//contains
if self.layerView.layer.contains(point!) {
if blueLayer.contains(point!) {
print("contain")
}
}
//hittest
let layer = self.layerView.layer.hitTest(point!)
if layer == blueLayer {
print("contain")
}
}
CALayer的錨點anchorPoint可以修改layer的位置:
//這是一個時鐘的例子禁悠,在xib里讓時鐘分鐘秒鐘的center相同,然后對錨點進(jìn)行調(diào)整兑宇,使三個圖片的一端在同一個地方碍侦。
override func viewDidLoad() {
super.viewDidLoad()
changeAnchorPoint()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
}
extension CALayerTestController{
//修改圖片的錨點
func changeAnchorPoint(){
self.hour.layer.anchorPoint = CGPoint(x: 0.5, y: 0.9)
self.min.layer.anchorPoint = CGPoint(x: 0.5, y: 0.8)
self.second.layer.anchorPoint = CGPoint(x: 0.1, y: 0.5)
}
func tick(){
//從當(dāng)前時間拿到時分秒
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.hour,.minute,.second], from: Date())
let hourAngle = (CGFloat(components.hour!)/12.0) * CGFloat(Double.pi * 2.0)
let minAngle = (CGFloat(components.minute!)/12.0) * CGFloat(Double.pi * 2.0)
let secondAngle = (CGFloat(components.second!)/12.0) * CGFloat(Double.pi * 2.0)
//轉(zhuǎn)起來
self.hour.transform = CGAffineTransform(rotationAngle: hourAngle)
self.min.transform = CGAffineTransform(rotationAngle: minAngle)
self.second.transform = CGAffineTransform(rotationAngle: secondAngle)
}
}
CALayer的陰影shadow,陰影是根據(jù)寄宿圖的輪廓來確定的:
layerView.layer.shadowOffset = CGSize(width: 5, height: 5)
layerView.layer.shadowOpacity = 0.9
layerView.layer.shadowColor = UIColor.black.cgColor
layerView.layer.shadowRadius = 0
陰影和視圖直接的邊界線,為0邊界線最明顯
但是有一個問題瓷产,那就是如果圖層需要masksToBounds剪裁站玄,那么陰影就會被剪裁掉。那么要解決這個問題的方法就是用到兩個圖層:一個畫陰影的外圖層濒旦,一個用masksToBounds剪裁內(nèi)容的內(nèi)圖層株旷。(其實就是在需要陰影的view下面插入一個view,然后對其陰影進(jìn)行設(shè)置疤估,這樣在視覺效果上就是一樣的了)
let newShadowViewLayer = UIView()
newShadowViewLayer.backgroundColor = UIColor.white //必須要有顏色或其他填充物灾常,否則就沒有寄宿圖,而陰影正好是圍繞寄宿圖的輪廓來確定的
newShadowViewLayer.frame = CGRect(x: (ContentWidth-200)/2, y: (ContentHeight-200)/2+220, width: 200, height: 200)
newShadowViewLayer.layer.shadowOffset = CGSize(width: 5, height: 5)
newShadowViewLayer.layer.shadowOpacity = 0.5
newShadowViewLayer.layer.shadowColor = UIColor.black.cgColor
newShadowViewLayer.layer.shadowRadius = 0
newShadowViewLayer.layer.cornerRadius = 4.0
newShadowViewLayer.layer.masksToBounds = false
self.view.addSubview(newShadowViewLayer) //注意不是加在layerView上,而且在layerView的更底部圖層上铃拇,如果和self.view.addSubview(layerView)調(diào)個位置钞瀑,則一樣沒有效果
layerView = UIView(frame: CGRect(x: (ContentWidth-200)/2, y: (ContentHeight-200)/2+220, width: 200, height: 200))
layerView.backgroundColor = UIColor.red
self.view.addSubview(layerView)
shadowPath屬性,實時計算陰影是非常耗資源的一件事情慷荔,尤其是有多個字圖層雕什,而且還有透明效果的時候。所以為了提高性能就可以用Core Graphics提供的CGPath給圖層設(shè)置任意形狀的陰影:
//給鐘表添加一個圓形的陰影
self.clock.layer.shadowOpacity = 0.5
let circlePath = CGMutablePath() //CGPath
circlePath.addEllipse(in: self.clock.bounds)
// self.clock.layer.shadowPath = circlePath
//給鐘表添加一個矩形的陰影
let squarePath = CGMutablePath()
squarePath.addRect(self.clock.bounds)
self.clock.layer.shadowPath = squarePath
圖層蒙版Mask显晶, 給一張圖片加蒙版贷岸,那么就會被剪裁成擁有蒙版的外形(待續(xù))
Core Graphics 實現(xiàn)繪圖的話會越來越慢!
總結(jié):
1磷雇、UIView有一個CALayer的類型屬性layer偿警;而所有與繪圖和坐標(biāo)相關(guān)的屬性及動畫實際都是訪問的layer的相關(guān)屬性;
2唯笙、UIView繼承自UIResponder螟蒸,所以能接受響應(yīng)事件,CALayer繼承自NSObject崩掘,不能響應(yīng)事件七嫌;
3、UIView可以有多個CALayer苞慢;