Swift語言iOS開發:CALayer十則示例

如你所知,咱們在iOS應用中看到的都是視圖(view),包括按鈕視圖、表視圖、滑動條視圖,還有能夠容納其餘視圖的父視圖等。swift

AD:【活動】Web和APP兼容性實戰 Win10訓練營免費報名 數組

如你所知,咱們在iOS應用中看到的都是視圖(view),包括按鈕視圖、表視圖、滑動條視圖,還有能夠容納其餘視圖的父視圖等。緩存

但你或許不知道在iOS中支撐起每一個視圖的是一個叫作"圖層(layer)"的類,確切地說是CALayer。app

本文中您會了解CALayer及其工做原理,還有應用CALayer打造酷炫效果的十則示例,好比繪製矢量圖形、漸變色,甚至是粒子系統。框架

本文要求讀者熟悉iOS應用開發和Swift語言的基礎知識,包括利用Storyboard構建用戶界面。異步

注:若是您還沒有掌握這些基礎,沒必要擔憂,咱們有很多相關教程,例如使用Swift語言編寫iOS應用和iOS學徒。ide

準備開始函數

要理解圖層是什麼,最簡便的方式就是"實地考察"。咱們這就建立一個簡單的項目,從頭開始玩轉圖層。工具

準備好寫代碼了嗎?好!啓動Xcode,而後:佈局

1.選擇File\New\Project菜單項。

2.在對話框中選擇iOS\Application\Single View Application。

3.點擊Next,Product Name填寫CALayerPlayground,而後輸入你本身的Organization Name和Identifier。

4.Language選Swift,Devices選Universal。

5.取消選擇Core Data,點擊Next。

6.把項目保存到合適的位置(我的習慣把項目放在用戶目錄下創建的Source文件夾),點擊Create。

好,文件準備就緒,接下來就是建立視圖了:

7.在項目導航欄(Project navigator)中選擇Main.storyboard。

8.選擇View\Assistant Editor\Show Assistant Editor菜單項,若是沒有顯示對象庫(Object Library),請選擇View\Utilities\Show Object Library。

9.而後選擇Editor\Canvas\Show Bounds Rectangles,這樣在向場景添加視圖時就能夠看到輪廓了。

10.把一個視圖(View)從對象庫拖入視圖控制器場景,保持選中狀態,在尺寸檢查器(View\Utilities\Show Size Inspector)中將x和y設爲150,Width和Height設爲300。

11.視圖保持選中,點擊自動佈局工具欄(Storyboard右下角)的Align按鈕,選中Horizontal Center in Container和Vertical Center in Container,數值均爲0,而後點擊Add 2 Constraints。

12.點擊Pin按鈕,選中Width和Height,數值均設爲300,點擊Add 2 Constraints。

最後按住control從剛剛建立的視圖拖到ViewController.swift文件中viewDidLoad()方法的上方,在彈框中將outlet命名爲viewForLayer,如圖:

點擊Connect建立outlet。

將ViewController.swift中的代碼改寫爲:

  1. import UIKit 
  2.  
  3. class ViewController: UIViewController { 
  4.  
  5. @IBOutlet weak var viewForLayer: UIView! 
  6.  
  7. var l: CALayer { 
  8. return viewForLayer.layer 
  9.  
  10. override func viewDidLoad() { 
  11. super.viewDidLoad() 
  12. setUpLayer() 
  13.  
  14. func setUpLayer() { 
  15. l.backgroundColor = UIColor.blueColor().CGColor 
  16. l.borderWidth = 100.0 
  17. l.borderColor = UIColor.redColor().CGColor 
  18. l.shadowOpacity = 0.7 
  19. l.shadowRadius = 10.0 
  20.  

以前提到iOS中的每一個視圖都擁有一個關聯的圖層,你能夠經過yourView.layer訪問圖層。這段代碼首先建立了一個叫"l"(小寫L)的計算屬性,方便訪問viewForLayer的圖層,可以讓你少寫一些代碼。

這段代碼還調用了setUpLayer方法設置圖層屬性:陰影,藍色背景,紅色粗邊框。你立刻就能夠了解這些東西,不過如今仍是先構建App,在iOS模擬器中運行(我選了iPhone 6),看看自定義的圖層如何。

幾行代碼,效果還不錯吧?仍是那句話,每一個視圖都由圖層支撐,因此你也能夠對App中的任何視圖作出相似修改。咱們繼續深刻。

CALayer基本屬性

CALayer有幾個屬性能夠用來自定外觀,想一想剛纔作的:

把圖層背景色從默認的無色改成藍色

經過把邊框寬度從默認的0改成100來添加邊框

把邊框顏色從默認的黑色改成紅色

最後把陰影透明度從0(全透明)改成0.7,產生陰影效果,此外還把陰影半徑從默認的3改成10。

以上只是CALayer中能夠設置的部分屬性。咱們再試兩個,在setUpLayer()中追加如下代碼:

  1. l.contents = UIImage(named: "star")?.CGImage 
  2. l.contentsGravity = kCAGravityCenter 

CALayer的contents屬性能夠把圖層的內容設爲圖片,這裏咱們要設置一張"星星"的圖片,爲此你須要把圖片添加到項目中,請下載圖片並添加到項目中。

構建,運行,欣賞一下效果:

注意星星居中,這是由於contentsGravity屬性被設爲kCAGravityCenter,如你所想,重心也能夠設爲上、右上、右、右下、下、左下、左、左上。

更改圖層外觀

僅供娛樂,咱們來添加幾個手勢識別器來控制圖層外觀。在Xcode中,向viewForLayer對象上拖一個輕觸手勢識別器(tap gesture recognizer),見下圖:

注:若是你對手勢識別器比較陌生,請參閱Using UIGestureRecognizer with Swift。

以此類推,再添加一個捏合手勢識別器(pinch gesture recognizer)。

而後按住control依次將兩個手勢識別器從Storyboard場景停靠欄拖入ViewController.swift,放在setUpLayer()和類自身的閉合花括號之間。

在彈框中修改鏈接爲Action,命名輕觸識別操做爲tapGestureRecognized,捏合識別操做爲pinchGestureRecognized,例如:

以下改寫tapGestureRecognized(_:):
 

  1. @IBAction func tapGestureRecognized(sender: UITapGestureRecognizer) { 
  2. l.shadowOpacity = l.shadowOpacity == 0.7 ? 0.0 : 0.7 

當令視圖識別出輕觸手勢時,代碼告知viewForLayer圖層在0.7和0之間切換陰影透明度。

你說視圖?嗯,沒錯,重寫CALayer的hitTest(_:)也能夠實現相同效果,本文後面也會看到這個方法,不過咱們這裏用的方法也有道理:圖層自己並不能響應手勢識別,只能響應點擊測試,因此咱們在視圖上設置了輕觸手勢識別器。

而後以下修改pinchGestureRecognized(_:):

  1. @IBAction func pinchGestureRecognized(sender: UIPinchGestureRecognizer) { 
  2. let offset: CGFloat = sender.scale < 1 ? 5.0 : -5.0 
  3. let oldFrame = l.frame 
  4. let oldOrigin = oldFrame.origin 
  5. let newOrigin = CGPoint(x: oldOrigin.x + offset, y: oldOrigin.y + offset) 
  6. let newSize = CGSize(width: oldFrame.width + (offset * -2.0), height: oldFrame.height + (offset * -2.0)) 
  7. let newFrame = CGRect(origin: newOrigin, size: newSize) 
  8. if newFrame.width >= 100.0 && newFrame.width <= 300.0 { 
  9. l.borderWidth -= offset 
  10. l.cornerRadius += (offset / 2.0) 
  11. l.frame = newFrame 

此處基於用戶的捏合手勢建立正負偏移值,藉此調整圖層框架大小、邊緣寬度和邊角半徑。

圖層的邊角半徑默認值爲0,意即標準的90度直角。增大半徑會產生圓角,若是想將圖層變成圓形,能夠設邊角半徑爲寬度的一半。

注意:調整邊角半徑並不會裁剪圖層內容(星星圖片),除非圖層的masksToBounds屬性被設爲true。

構建運行,嘗試在視圖中使用輕觸和捏合手勢:

嘿,再好好裝扮一下都能當頭像用了! :]

CALayer體驗

CALayer中的屬性和方法琳琅滿目,此外還有幾個包含特有屬性和方法的子類。

要遍歷如此酷炫的API,Raywenderlich.com導遊先生最好不過了。

接下來,你須要如下材料:

Layer Player App

Layer Player 源代碼

該App包含十種不一樣的CALayer示例,本文後面會依次介紹,十分方便。先來吊吊你們的胃口:

下面在講解每一個示例的同時,我建議在CALayer演示應用中親自動手試驗,還能夠讀讀代碼。不用寫,只要深呼吸,輕鬆閱讀就能夠了。 :]

我相信這些酷炫的示例會啓發您利用不一樣的CALayer爲本身的App錦上添花,但願你們喜歡!

示例 #1:CALayer

前面咱們看過使用CALayer的示例,也就是設置各類屬性。

關於CALayer還有幾點沒提:

圖層能夠包含子圖層。就像視圖能夠包含子視圖,圖層也能夠有子圖層,稍加利用就能打造漂亮的效果!

圖層屬性自帶動畫效果。修改圖層屬性時,存在默認的動畫效果,你也能夠自定義動畫行爲。

圖層是輕量概念。相對視圖而言,圖層更加輕量,所以圖層能夠幫助提高性能。

圖層有大量實用屬性。前面你已經看過幾條了,咱們繼續探索!

剛剛說CALayer圖層有不少屬性,咱們來看一批實用屬性:有些屬性你可能第一次見,但真的很方便!

  1. // 1 
  2. let layer = CALayer() 
  3. layer.frame = someView.bounds 
  4.  
  5. // 2 
  6. layer.contents = UIImage(named: "star")?.CGImage 
  7. layer.contentsGravity = kCAGravityCenter 
  8.  
  9. // 3 
  10. layer.magnificationFilter = kCAFilterLinear 
  11. layer.geometryFlipped = false 
  12.  
  13. // 4 
  14. layer.backgroundColor = UIColor(red: 11/255.0, green: 86/255.0, blue: 14/255.0, alpha: 1.0).CGColor 
  15. layer.opacity = 1.0 
  16. layer.hidden = false 
  17. layer.masksToBounds = false 
  18.  
  19. // 5 
  20. layer.cornerRadius = 100.0 
  21. layer.borderWidth = 12.0 
  22. layer.borderColor = UIColor.whiteColor().CGColor 
  23.  
  24. // 6 
  25. layer.shadowOpacity = 0.75 
  26. layer.shadowOffset = CGSize(width: 0, height: 3) 
  27. layer.shadowRadius = 3.0 
  28. someView.layer.addSublayer(layer) 

在以上代碼中:

建立一個CALayer實例,並把框架設爲someView邊框。

將圖層內容設爲一張圖片,並使其在圖層內居中,注意賦值的類型是底層的Quartz圖像數據(CGImage)。

使用過濾器,過濾器在圖像利用contentsGravity放大時發揮做用,可用於改變大小(縮放、比例縮放、填充比例縮放)和位置(中心、上、 右上、右等等)。以上屬性的改變沒有動畫效果,另外若是geometryFlipped未設爲true,幾何位置和陰影會上下顛倒。繼續:

把背景色設爲Ray最愛的深綠色。:] 而後讓圖層透明、可見。同時令圖層不要遮罩內容,意思是若是圖層尺寸小於內容(星星圖片),圖像不會被裁減。

圖層邊角半徑設爲圖層寬度的一半,使邊緣變爲圓形,注意圖層顏色賦值類型爲Quartz顏色引用(CGColor)。

建立陰影,設shouldRasterize爲true(後文還會提到),而後將圖層加入視圖結構樹。

結果以下:

CALayer還有兩個附加屬性有助於改善性能:shouldRasterize和drawsAsynchronously。

shouldRasterize默認爲false,設爲true能夠改善性能,由於圖層內容只須要一次渲染。相對畫面中移動但自身外觀不變的對象效果拔羣。

drawsAsynchronously默認值也是false。與shouldRasterize相對,該屬性適用於圖層內容須要反覆重繪的狀況, 此時設成true可能會改善性能,好比須要反覆繪製大量粒子的粒子發射器圖層(能夠參考後面的CAEmitterLayer示例)。

謹記:若是想將已有圖層的shouldRasterize或drawsAsynchronously屬性設爲true,必定要三思然後行,考慮可能形成的影響,對比true與false的性能差別,辨明屬性設置是否有積極效果。設置不當甚至會致使性能大幅降低。

不管如何仍是先回到圖層演示應用,其中有些控件能夠用來調整CALayer的屬性:

調節試試看,感覺一下,利用CALayer能夠實現怎樣的效果。

注:圖層不屬於響應鏈(responder chain),沒法像視圖同樣直接響應觸摸和手勢,咱們在CALayerPlayground中見識過。不過圖層有點擊測試,後面的 CATransformLayer會提到。你也能夠向圖層添加自定義動畫,CAReplicatorLayer中會出現。

示例 #2:CAScrollLayer

CAScrollLayer顯示一部分可滾動圖層,該圖層十分基礎,沒法直接響應用戶的觸摸操做,也不能直接檢查可滾動圖層的邊界,故可避免越界無限滾動。

UIScrollView用的不是CAScrollLayer,而是直接改動圖層邊界。

CAScrollLayer的滾動模式可設爲水平、垂直或者二維,你也能夠用代碼命令視圖滾動到指定位置:

  1. // In ScrollingView.swift 
  2. import UIKit 
  3.  
  4. class ScrollingView: UIView { 
  5. // 1 
  6. override class func layerClass() -> AnyClass { 
  7. return CAScrollLayer.self 
  8.  
  9. // In CAScrollLayerViewController.swift 
  10. import UIKit 
  11.  
  12. class CAScrollLayerViewController: UIViewController { 
  13. @IBOutlet weak var scrollingView: ScrollingView! 
  14.  
  15. // 2 
  16. var scrollingViewLayer: CAScrollLayer { 
  17. return scrollingView.layer as CAScrollLayer 
  18.  
  19. override func viewDidLoad() { 
  20. super.viewDidLoad() 
  21. // 3 
  22. scrollingViewLayer.scrollMode = kCAScrollBoth 
  23.  
  24. @IBAction func tapRecognized(sender: UITapGestureRecognizer) { 
  25. // 4 
  26. var newPoint = CGPoint(x: 250, y: 250) 
  27. UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: { 
  28. [unowned self] in 
  29. self.scrollingViewLayer.scrollToPoint(newPoint) 
  30. }, completion: nil) 
  31.  

以上代碼:

定義一個繼承UIView的類,重寫layerClass()返回CAScrollLayer,該方法等同於建立一個新圖層做爲子圖層(CALayer示例中作過)。

一個用以方便簡化訪問自定義視圖滾動圖層的計算屬性。

設滾動模式爲二維滾動。

識別出輕觸手勢時,讓滾動圖層在UIView動畫中滾到新建的點。(注:scrollToPoint(_:)和scrollToRect(_:)不會自動使用動畫效果。)

案例研究:若是ScrollingView實例包含大於滾動視圖邊界的圖片視圖,在運行上述代碼並點擊視圖時結果以下:

圖層演示應用中有能夠鎖定滾動方向(水平或垂直)的開關。

如下經驗規律用於決定是否使用CAScrollLayer:

若是想使用輕量級的對象,只需用代碼操做滾動:能夠考慮CAScrollLayer。

若是想讓用戶操做滾動,UIScrollView大概是更好的選擇。要了解更多,請參考咱們的視頻教程。

若是是滾動大型圖片:考慮使用CATiledLayer(見後文)。

示例 #3:CATextLayer

CATextLayer可以對普通文本或屬性字串進行簡單快速的渲染。與UILabel不一樣,CATextLayer沒法指定UIFont,只能使用CTFontRef或CGFontRef。

像下面這樣的代碼徹底能夠掌控文本的字體、字體大小、顏色、對齊、折行(wrap)和截斷(truncation)規則,也有動畫效果:

  1. // 1 
  2. let textLayer = CATextLayer() 
  3. textLayer.frame = someView.bounds 
  4.  
  5. // 2 
  6. var string = "" 
  7. for _ in 1...20 { 
  8. string += "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce auctor arcu quis velit congue dictum. " 
  9.  
  10. textLayer.string = string 
  11.  
  12. // 3 
  13. let fontName: CFStringRef = "Noteworthy-Light" 
  14. textLayer.font = CTFontCreateWithName(fontName, fontSize, nil) 
  15.  
  16. // 4 
  17. textLayer.foregroundColor = UIColor.darkGrayColor().CGColor 
  18. textLayer.wrapped = true 
  19. textLayer.alignmentMode = kCAAlignmentLeft 
  20. textLayer.contentsScale = UIScreen.mainScreen().scale 
  21. someView.layer.addSublayer(textLayer) 

以上代碼解釋以下:

建立一個CATextLayer實例,令邊界與someView相同。

重複一段文本,建立字符串並賦給文本圖層。

建立一個字體,賦給文本圖層。

將文本圖層設爲折行、左對齊,你也能夠設天然對齊(natural)、右對齊(right)、居中對齊(center)或兩端對齊(justified),按屏幕設置contentsScale屬性,而後把圖層添加到視圖結構樹。

不只是CATextLayer,全部圖層類的渲染縮放係數都默認爲1。在添加到視圖時,圖層自身的contentsScale縮放係數會自動調整, 適應當前畫面。你須要爲手動建立的圖層明確指定contentsScale屬性,不然默認的縮放係數1會在Retina顯示屏上產生部分模糊。

若是建立的文本圖層添加到了方形的someView,效果會像這樣:

 

你能夠設置截斷(Truncation)屬性,生效時被截斷的部分文本會由省略號代替顯示。默認設定爲無截斷,位置可設爲開頭、末尾或中間截斷:

圖層演示應用中,你能夠爲所欲爲地修改不少CATextLayer屬性:

 

示例 #4:AVPlayerLayer

AVPlayerLayer是創建在AVFoundation基礎上的實用圖層,持有一個AVPlayer,用來播放音視頻媒體文件(AVPlayerItems),舉例以下:

  1. override func viewDidLoad() { 
  2. super.viewDidLoad() 
  3. // 1 
  4. let playerLayer = AVPlayerLayer() 
  5. playerLayer.frame = someView.bounds 
  6.  
  7. // 2 
  8. let url = NSBundle.mainBundle().URLForResource("someVideo", withExtension: "m4v") 
  9. let player = AVPlayer(URL: url) 
  10.  
  11. // 3 
  12. player.actionAtItemEnd = .None 
  13. playerLayer.player = player 
  14. someView.layer.addSublayer(playerLayer) 
  15.  
  16. // 4 
  17. NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidReachEndNotificationHandler:", name: "AVPlayerItemDidPlayToEndTimeNotification", object: player.currentItem) 
  18.  
  19. deinit { 
  20. NSNotificationCenter.defaultCenter().removeObserver(self) 
  21.  
  22. // 5 
  23. @IBAction func playButtonTapped(sender: UIButton) { 
  24. if playButton.titleLabel?.text == "Play" { 
  25. player.play() 
  26. playButton.setTitle("Pause", forState: .Normal) 
  27. else { 
  28. player.pause() 
  29. playButton.setTitle("Play", forState: .Normal) 
  30.  
  31. updatePlayButtonTitle() 
  32. updateRateSegmentedControl() 
  33.  
  34. // 6 
  35. func playerDidReachEndNotificationHandler(notification: NSNotification) { 
  36. let playerItem = notification.object as AVPlayerItem 
  37. playerItem.seekToTime(kCMTimeZero) 

上述代碼解釋:

新建一個播放器圖層,設置框架。

使用AV asset資源建立一個播放器。

告知命令播放器在播放完成後中止。其餘選項還有暫停或自動播放下一個媒體資源。

註冊AVPlayer通知,在一個文件播放完畢後發送通知,並在析構函數中刪除做爲觀察者的控制器。

點擊播放按鈕時,觸發控件播放AV asset並設置按鈕文字。

注意這只是個入門示例,在實際項目中每每不會採用文字按鈕控制播放。

AVPlayerLayer和其中建立的AVPlayer會像這樣顯示爲AVPlayerItem實例的第一幀:

AVPlayerLayer還有一些附加屬性:

videoGravity設置視頻顯示的縮放行爲。

readyForDisplay檢測是否準備好播放視頻。

另外一方面,AVPlayer也有很多附加屬性和方法,有一個值得注意的是rate屬性,對於0到1之間的播放速率,0表明暫停,1表明常速播放(1x)。

不過rate屬性的設置是與播放行爲聯動的,也就是說調用pause()方法和把rate設爲0是等價的,調用play()與把rate設爲1也同樣。

那快進、慢動做和反向播放呢?交給AVPlayerLayer把。rate大於1時會令播放器以相應倍速進行播放,例如rate設爲2就是二倍速。

如你所想,rate爲負時會讓播放器以相應倍速反向播放。

然而,在以很是規速率播放以前,AVPlayerItem上會調用適當方法,驗證是否可以以相應速率進行播放:

canPlayFastForward()對應大於1

canPlaySlowForward()對應0到1之間

canPlayReverse()對應-1

canPlaySlowReverse()對應-1到0之間

canPlayFastReverse()對應小於-1

絕大多數視頻都支持以不一樣速率正向播放,能夠反向播放的視頻相對少一些。演示應用也包含了播放控件:

示例 #5:CAGradientLayer

CAGradientLayer簡化了混合兩種或更多顏色的工做,尤爲適用於背景。要配置漸變色,你須要分配一個CGColor數組,以及標識漸變圖層起止點的startPoint和endPoint。

注意:startPoint和endPoint並非明確的點,而是用單位座標空間定義,在繪製時映射到圖層邊界。也就是說x值爲1表示點在圖層右邊緣,y值爲1表示點在圖層下邊緣。

CAGradientLayer包含type屬性,雖然說該屬性只有kCAGradientLayerAxial一個選擇,由數組中的各顏色產生線性過渡漸變。

具體含義是漸變過渡沿startPoint到endPoint的向量A方向產生,設B與A垂直,則各條B平行線上的全部點顏色相同。

此外,locations屬性可使用一個數組(元素取值範圍0到1),指定漸變圖層參照colors順序取用下一個過渡點顏色的位置。

未設定時默認會平均分配過渡點。一旦設定就必須與colors的數量保持一致,不然會出錯。 :[

下面是建立漸變圖層的例子:

  1. let gradientLayer = CAGradientLayer() 
  2. gradientLayer.frame = someView.bounds 
  3. gradientLayer.colors = [cgColorForRed(209.0, green: 0.0, blue: 0.0), 
  4. cgColorForRed(255.0, green: 102.0, blue: 34.0), 
  5. cgColorForRed(255.0, green: 218.0, blue: 33.0), 
  6. cgColorForRed(51.0, green: 221.0, blue: 0.0), 
  7. cgColorForRed(17.0, green: 51.0, blue: 204.0), 
  8. cgColorForRed(34.0, green: 0.0, blue: 102.0), 
  9. cgColorForRed(51.0, green: 0.0, blue: 68.0)] 
  10. gradientLayer.startPoint = CGPoint(x: 0, y: 0) 
  11. gradientLayer.endPoint = CGPoint(x: 0, y: 1) 
  12. someView.layer.addSublayer(gradientLayer) 



func cgColorForRed(red: CGFloat, green: CGFloat, blue: CGFloat) -> AnyObject {
return UIColor(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: 1.0).CGColor as AnyObject
}

上述代碼建立一個漸變圖層,框架設爲someView邊界,指定顏色數組,設置起止點,添加圖層到視圖結構樹。效果以下:

五彩繽紛,奼紫嫣紅!

圖層演示應用中,你能夠隨意修改起止點、顏色和過渡點:

示例 #6:CAReplicatorLayer

CAReplicatorLayer可以以特定次數複製圖層,能夠用來建立一些很棒的效果。

每一個圖層復件的顏色和位置均可以改動,並且能夠在總複製圖層以後延遲繪製,營造一種動畫效果。還能夠利用深度,創造三維效果。舉個例子

  1. // 1 
  2. let replicatorLayer = CAReplicatorLayer() 
  3. replicatorLayer.frame = someView.bounds 
  4.  
  5. // 2 
  6. replicatorLayer.instanceCount = 30 
  7. replicatorLayer.instanceDelay = CFTimeInterval(1 / 30.0) 
  8. replicatorLayer.preservesDepth = false 
  9. replicatorLayer.instanceColor = UIColor.whiteColor().CGColor 
  10.  
  11. // 3 
  12. replicatorLayer.instanceRedOffset = 0.0 
  13. replicatorLayer.instanceGreenOffset = -0.5 
  14. replicatorLayer.instanceBlueOffset = -0.5 
  15. replicatorLayer.instanceAlphaOffset = 0.0 
  16.  
  17. // 4 
  18. let angle = Float(M_PI * 2.0) / 30 
  19. replicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0) 
  20. someView.layer.addSublayer(replicatorLayer) 
  21.  
  22. // 5 
  23. let instanceLayer = CALayer() 
  24. let layerWidth: CGFloat = 10.0 
  25. let midX = CGRectGetMidX(someView.bounds) - layerWidth / 2.0 
  26. instanceLayer.frame = CGRect(x: midX, y: 0.0, width: layerWidth, height: layerWidth * 3.0) 
  27. instanceLayer.backgroundColor = UIColor.whiteColor().CGColor 
  28. replicatorLayer.addSublayer(instanceLayer) 
  29.  
  30. // 6 
  31. let fadeAnimation = CABasicAnimation(keyPath: "opacity") 
  32. fadeAnimation.fromValue = 1.0 
  33. fadeAnimation.toValue = 0.0 
  34. fadeAnimation.duration = 
  35. fadeAnimation.repeatCount = Float(Int.max) 
  36.  
  37. // 7 
  38. instanceLayer.opacity = 0.0 
  39. instanceLayer.addAnimation(fadeAnimation, forKey: "FadeAnimation") 

以上代碼:

建立一個CAReplicatorLayer實例,設框架爲someView邊界。

設複製圖層數instanceCount和繪製延遲,設圖層爲2D(preservesDepth = false),實例顏色爲白色。

爲陸續的實例復件設置RGB顏色誤差值(默認爲0,即全部復件保持顏色不變),不過這裏實例初始顏色爲白色,即RGB都爲1.0,因此誤差值設紅色爲0,綠色和藍色爲相同負數會使其逐漸現出紅色,alpha透明度誤差值的變化也與此相似,針對陸續的實例復件。

建立旋轉變換,使得實例復件按一個圓排列。

建立供複製圖層使用的實例圖層,設置框架,使第一個實例在someView邊界頂端水平中心處繪製,另外設置實例顏色,把實例圖層添加到複製圖層。

建立一個透明度由1(不透明)過渡爲0(透明)的淡出動畫。

設實例圖層透明度爲0,使得每一個實例在繪製和改變顏色與alpha前保持透明。

這段代碼會實現這樣的東西:

圖層演示應用中,你能夠改動這些屬性:

示例 #7:CATiledLayer

CATiledLayer以圖塊(tile)爲單位異步繪製圖層內容,對超大尺寸圖片或者只能在視圖中顯示一小部分的內容效果拔羣,由於不用把內容徹底載入內存就能夠看到內容。

處理繪製有幾種方法,一種是重寫UIView,使用CATiledLayer繪製圖塊填充視圖背景,以下:

  1. // In ViewController.swift 
  2. import UIKit 
  3.  
  4. class ViewController: UIViewController { 
  5.  
  6. // 1 
  7. @IBOutlet weak var tiledBackgroundView: TiledBackgroundView! 
  8.  
  9.  
  10. // In TiledBackgroundView.swift 
  11. import UIKit 
  12.  
  13. class TiledBackgroundView: UIView { 
  14.  
  15. let sideLength = CGFloat(50.0) 
  16.  
  17. // 2 
  18. override class func layerClass() -> AnyClass { 
  19. return CATiledLayer.self 
  20.  
  21. // 3 
  22. required init(coder aDecoder: NSCoder) { 
  23. super.init(coder: aDecoder) 
  24. srand48(Int(NSDate().timeIntervalSince1970)) 
  25. let layer = self.layer as CATiledLayer 
  26. let scale = UIScreen.mainScreen().scale 
  27. layer.contentsScale = scale 
  28. layer.tileSize = CGSize(width: sideLength * scale, height: sideLength * scale) 
  29.  
  30. // 4 
  31. override func drawRect(rect: CGRect) { 
  32. let context = UIGraphicsGetCurrentContext() 
  33. var red = CGFloat(drand48()) 
  34. var green = CGFloat(drand48()) 
  35. var blue = CGFloat(drand48()) 
  36. CGContextSetRGBFillColor(context, red, green, blue, 1.0) 
  37. CGContextFillRect(context, rect) 
  38.  

代碼解釋:

tiledBackgroundView位於 (150, 150) ,寬高均爲300。

重寫layerClass(),令該視圖建立的圖層實例爲CATiledLayer。

設置rand48()的隨機數種子,用於在drawRect()中生成隨機顏色。CATiledLayer類型轉換,縮放圖層內容,設置圖塊尺寸,適應屏幕。

重寫drawRect(),以隨機色塊填充視圖。

代碼繪製6×6隨機色塊方格,最終效果以下:

圖層演示應用中除此以外還能夠在圖層背景上繪製軌跡:

在視圖中放大時,上述截圖中的星星圖案會變得模糊:

產生模糊的根源是圖層的細節層次(level of detail,簡稱LOD),CATiledLayer有兩個相關屬性:levelsOfDetail和levelsOfDetailBias。

levelsOfDetail顧名思義,指圖層維護的LOD數目,默認值爲1,每進一級會對前一級分辨率的一半進行緩存,圖層的levelsOfDetail最大值,也就是最底層細節,對應至少一個像素點。

而levelsOfDetailBias指的是該圖層緩存的放大LOD數目,默認爲0,即不會額外緩存放大層次,每進一級會對前一級兩倍分辨率進行緩存。

例如,設上述分塊圖層的levelsOfDetailBias爲5會緩存2x、4x、8x、16x和32x的放大層次,放大的圖層效果以下:

 

不錯吧?彆着急,還沒講完呢。

CATiledLayer裁刀,買不了吃虧,買不了上當,只要998…(譯註:此處內容稍做本地化處理,原文玩的是1978年美國Ginsu刀具的梗,堪稱詢價型電視購物廣告的萬惡之源。) :]

開個玩笑。CATiledLayer還有一個更實用的功能:異步繪製圖塊,好比在滾動視圖中顯示一張超大圖片。

在用戶滾動畫面時,要讓分塊圖層知道哪些圖塊須要繪製,寫代碼在所不免,不過換來性能提高也值了。

圖層演示應用的UIImage+TileCutter.swift中包含一個UIImage擴展,教程編纂組成員Nick Lockwood在著做iOS Core Animation: Advanced Techniques的一個終端應用程序中利用了這段代碼。

代碼的職責是把原圖片拆分紅指定尺寸的方塊,按行列位置命名圖塊,好比第三行第七列的圖塊windingRoad62.png(索引從零開始)。

 

有了這些圖塊,咱們能夠自定義一個UIView子類,繪製分塊圖層:

  1. mport UIKit 
  2.  
  3. class TilingViewForImage: UIView { 
  4.  
  5. // 1 
  6. let sideLength = CGFloat(640.0) 
  7. let fileName = "windingRoad" 
  8. let cachesPath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as String 
  9.  
  10. // 2 
  11. override class func layerClass() -> AnyClass { 
  12. return CATiledLayer.self 
  13.  
  14. // 3 
  15. required init(coder aDecoder: NSCoder) { 
  16. super.init(coder: aDecoder) 
  17. let layer = self.layer as CATiledLayer 
  18. layer.tileSize = CGSize(width: sideLength, height: sideLength) 
  19.  
  20. // 4 
  21. override func drawRect(rect: CGRect) { 
  22. let firstColumn = Int(CGRectGetMinX(rect) / sideLength) 
  23. let lastColumn = Int(CGRectGetMaxX(rect) / sideLength) 
  24. let firstRow = Int(CGRectGetMinY(rect) / sideLength) 
  25. let lastRow = Int(CGRectGetMaxY(rect) / sideLength) 
  26.  
  27. for row in firstRow...lastRow { 
  28. for column in firstColumn...lastColumn { 
  29. if let tile = imageForTileAtColumn(column, row: row) { 
  30. let x = sideLength * CGFloat(column) 
  31. let y = sideLength * CGFloat(row) 
  32. let point = CGPoint(x: x, y: y) 
  33. let size = CGSize(width: sideLength, height: sideLength) 
  34. var tileRect = CGRect(origin: point, size: size) 
  35. tileRect = CGRectIntersection(bounds, tileRect) 
  36. tile.drawInRect(tileRect) 
  37.  
  38. func imageForTileAtColumn(column: Int, row: Int) -> UIImage? { 
  39. let filePath = "\(cachesPath)/\(fileName)_\(column)_\(row)" 
  40. return UIImage(contentsOfFile: filePath) 
  41.  

以上代碼:

建立屬性,分別是圖塊邊長、原圖文件名、供TileCutter擴展保存圖塊的緩存文件夾路徑。

重寫layerClass()返回CATiledLayer。

實現init(_:),把視圖的圖層轉換爲分塊圖層,設置圖塊大小。注意此處沒必要設置contentsScale適配屏幕,由於是直接修改視圖自身的圖層,而不是手動建立子圖層。

重寫drawRect(),按行列繪製各個圖塊。

像這樣,原圖大小的自定義視圖就能夠塞進一個滾動視圖:

多虧CATiledLayer,滾動5120 x 3200的大圖也會這般順滑:

如你所見,快速滾動時繪製圖塊的過程仍是很明顯,你能夠利用更小的分塊(上述例子中分塊爲640 x 640),或者本身建立一個CATiledLayer子類,重寫fadeDuration()返回0:

  1. class TiledLayer: CATiledLayer { 
  2.  
  3. override class func fadeDuration() -> CFTimeInterval { 
  4. return 0.0 
  5.  

示例 #8:CAShapeLayer

CAShapeLayer利用可縮放的矢量路徑進行繪製,繪製速度比使用圖片快不少,還有個好處是不用分別提供常規、@2x和@3x版本的圖片,好用。

另外還有各類屬性,讓你能夠自定線粗、顏色、虛實、線條接合方式、閉合線條是否造成閉合區域,還有閉合區域要填充何種顏色等。舉例以下

  1. import UIKit 
  2.    
  3. class ViewController: UIViewController { 
  4.    
  5.   @IBOutlet weak var someView: UIView! 
  6.    
  7.   // 1 
  8.   let rwColor = UIColor(red: 11/255.0, green: 86/255.0, blue: 14/255.0, alpha: 1.0) 
  9.   let rwPath = UIBezierPath() 
  10.   let rwLayer = CAShapeLayer() 
  11.    
  12.   // 2 
  13.   func setUpRWPath() { 
  14.     rwPath.moveToPoint(CGPointMake(0.22, 124.79)) 
  15.     rwPath.addLineToPoint(CGPointMake(0.22, 249.57)) 
  16.     rwPath.addLineToPoint(CGPointMake(124.89, 249.57)) 
  17.     rwPath.addLineToPoint(CGPointMake(249.57, 249.57)) 
  18.     rwPath.addLineToPoint(CGPointMake(249.57, 143.79)) 
  19.     rwPath.addCurveToPoint(CGPointMake(249.37, 38.25), controlPoint1: CGPointMake(249.57, 85.64), controlPoint2: CGPointMake(249.47, 38.15)) 
  20.     rwPath.addCurveToPoint(CGPointMake(206.47, 112.47), controlPoint1: CGPointMake(249.27, 38.35), controlPoint2: CGPointMake(229.94, 71.76)) 
  21.     rwPath.addCurveToPoint(CGPointMake(163.46, 186.84), controlPoint1: CGPointMake(182.99, 153.19), controlPoint2: CGPointMake(163.61, 186.65)) 
  22.     rwPath.addCurveToPoint(CGPointMake(146.17, 156.99), controlPoint1: CGPointMake(163.27, 187.03), controlPoint2: CGPointMake(155.48, 173.59)) 
  23.     rwPath.addCurveToPoint(CGPointMake(128.79, 127.08), controlPoint1: CGPointMake(136.82, 140.43), controlPoint2: CGPointMake(129.03, 126.94)) 
  24.     rwPath.addCurveToPoint(CGPointMake(109.31, 157.77), controlPoint1: CGPointMake(128.59, 127.18), controlPoint2: CGPointMake(119.83, 141.01)) 
  25.     rwPath.addCurveToPoint(CGPointMake(89.83, 187.86), controlPoint1: CGPointMake(98.79, 174.52), controlPoint2: CGPointMake(90.02, 188.06)) 
  26.     rwPath.addCurveToPoint(CGPointMake(56.52, 108.28), controlPoint1: CGPointMake(89.24, 187.23), controlPoint2: CGPointMake(56.56, 109.11)) 
  27.     rwPath.addCurveToPoint(CGPointMake(64.02, 102.25), controlPoint1: CGPointMake(56.47, 107.75), controlPoint2: CGPointMake(59.24, 105.56)) 
  28.     rwPath.addCurveToPoint(CGPointMake(101.42, 67.57), controlPoint1: CGPointMake(81.99, 89.78), controlPoint2: CGPointMake(93.92, 78.72)) 
  29.     rwPath.addCurveToPoint(CGPointMake(108.38, 30.65), controlPoint1: CGPointMake(110.28, 54.47), controlPoint2: CGPointMake(113.01, 39.96)) 
  30.     rwPath.addCurveToPoint(CGPointMake(10.35, 0.41), controlPoint1: CGPointMake(99.66, 13.17), controlPoint2: CGPointMake(64.11, 2.16)) 
  31.     rwPath.addLineToPoint(CGPointMake(0.22, 0.07)) 
  32.     rwPath.addLineToPoint(CGPointMake(0.22, 124.79)) 
  33.     rwPath.closePath() 
  34.   } 
  35.    
  36.   // 3 
  37.   func setUpRWLayer() { 
  38.     rwLayer.path = rwPath.CGPath 
  39.     rwLayer.fillColor = rwColor.CGColor 
  40.     rwLayer.fillRule = kCAFillRuleNonZero 
  41.     rwLayer.lineCap = kCALineCapButt 
  42.     rwLayer.lineDashPattern = nil 
  43.     rwLayer.lineDashPhase = 0.0 
  44.     rwLayer.lineJoin = kCALineJoinMiter 
  45.     rwLayer.lineWidth = 1.0 
  46.     rwLayer.miterLimit = 10.0 
  47.     rwLayer.strokeColor = rwColor.CGColor 
  48.   } 
  49.    
  50.   override func viewDidLoad() { 
  51.     super.viewDidLoad() 
  52.    
  53.     // 4 
  54.     setUpRWPath() 
  55.     setUpRWLayer() 
  56.     someView.layer.addSublayer(rwLayer) 
  57.   } 
  58.    
  59. }

代碼解釋:

建立顏色、路徑、圖形圖層對象。

繪製圖形圖層路徑。若是不喜歡編寫生硬的繪圖代碼的話,你能夠嘗試PaintCode這款軟件,能夠利用簡便的工具進行可視化繪製,支持導入現有的矢量圖(SVG)和Photoshop(PSD)文件,並自動生成代碼。

設置圖形圖層。路徑設爲第二步中繪製的CGPath路徑,填充色設爲第一步中建立的CGColor顏色,填充規則設爲非零(non-zero),即默認填充規則。

填充規則共有兩種,另外一種是奇偶(even-odd)。不過示例代碼中的圖形沒有相交路徑,兩種填充規則的結果並沒有差別。

非零規則記從左到右的路徑爲+1,從右到左的路徑爲-1,累加全部路徑值,若總和大於零,則填充路徑圍成的圖形。

從結果上來說,非零規則會填充圖形內部全部的點。

奇偶規則計算圍成圖形的路徑交叉數,若結果爲奇數則填充。這樣講有些晦澀,仍是有圖有真相:

右圖圍成中間五邊形的路徑交叉數爲偶數,故中間沒有填充,而圍成每一個三角的路徑交叉數爲奇數,故三角部分填充顏色。

調用路徑繪製和圖層設置代碼,並把圖層添加到視圖結構樹。

上述代碼繪製raywenderlich.com的圖標:

順便看看使用PaintCode的效果圖:

圖層演示應用中,你能夠隨意修改不少CAShapeLayer屬性:

注:咱們先跳過演示應用中的下一個示例,由於CAEAGLLayer多少顯得有些過期了,iOS 8 Metal框架有更先進的CAMetalLayer。在此推薦iOS 8 Metal入門教程。

示例 #9:CATransformLayer

CATransformLayer不像其餘圖層類同樣把子圖層結構平面化,故適宜繪製3D結構。變換圖層本質上是一個圖層容器,每一個子圖層均可以應用本身的透明度和空間變換,而其餘渲染圖層屬性(如邊寬、顏色)會被忽略。

變換圖層自己不支持點擊測試,由於沒法直接在觸摸點和平面座標空間創建映射,不過其中的子圖層能夠響應點擊測試,例如:

  1. import UIKit 
  2.  
  3. class ViewController: UIViewController { 
  4.  
  5. @IBOutlet weak var someView: UIView! 
  6.  
  7. // 1 
  8. let sideLength = CGFloat(160.0) 
  9. var redColor = UIColor.redColor() 
  10. var orangeColor = UIColor.orangeColor() 
  11. var yellowColor = UIColor.yellowColor() 
  12. var greenColor = UIColor.greenColor() 
  13. var blueColor = UIColor.blueColor() 
  14. var purpleColor = UIColor.purpleColor() 
  15. var transformLayer = CATransformLayer() 
  16.  
  17. // 2 
  18. func setUpTransformLayer() { 
  19. var layer = sideLayerWithColor(redColor) 
  20. transformLayer.addSublayer(layer) 
  21.  
  22. layer = sideLayerWithColor(orangeColor) 
  23. var transform = CATransform3DMakeTranslation(sideLength / 2.0, 0.0, sideLength / -2.0) 
  24. transform = CATransform3DRotate(transform, degreesToRadians(90.0), 0.0, 1.0, 0.0) 
  25. layer.transform = transform 
  26. transformLayer.addSublayer(layer) 
  27.  
  28. layer = sideLayerWithColor(yellowColor) 
  29. layer.transform = CATransform3DMakeTranslation(0.0, 0.0, -sideLength) 
  30. transformLayer.addSublayer(layer) 
  31.  
  32. layer = sideLayerWithColor(greenColor) 
  33. transform = CATransform3DMakeTranslation(sideLength / -2.0, 0.0, sideLength / -2.0) 
  34. transform = CATransform3DRotate(transform, degreesToRadians(90.0), 0.0, 1.0, 0.0) 
  35. layer.transform = transform 
  36. transformLayer.addSublayer(layer) 
  37.  
  38. layer = sideLayerWithColor(blueColor) 
  39. transform = CATransform3DMakeTranslation(0.0, sideLength / -2.0, sideLength / -2.0) 
  40. transform = CATransform3DRotate(transform, degreesToRadians(90.0), 1.0, 0.0, 0.0) 
  41. layer.transform = transform 
  42. transformLayer.addSublayer(layer) 
  43.  
  44. layer = sideLayerWithColor(purpleColor) 
  45. transform = CATransform3DMakeTranslation(0.0, sideLength / 2.0, sideLength / -2.0) 
  46. transform = CATransform3DRotate(transform, degreesToRadians(90.0), 1.0, 0.0, 0.0) 
  47. layer.transform = transform 
  48. transformLayer.addSublayer(layer) 
  49.  
  50. transformLayer.anchorPointZ = sideLength / -2.0 
  51. applyRotationForXOffset(16.0, yOffset: 16.0) 
  52.  
  53. // 3 
  54. func sideLayerWithColor(color: UIColor) -> CALayer { 
  55. let layer = CALayer() 
  56. layer.frame = CGRect(origin: CGPointZero, size: CGSize(width: sideLength, height: sideLength)) 
  57. layer.position = CGPoint(x: CGRectGetMidX(someView.bounds), y: CGRectGetMidY(someView.bounds)) 
  58. layer.backgroundColor = color.CGColor 
  59. return layer 
  60.  
  61. func degreesToRadians(degrees: Double) -> CGFloat { 
  62. return CGFloat(degrees * M_PI / 180.0) 
  63.  
  64. // 4 
  65. func applyRotationForXOffset(xOffset: Double, yOffset: Double) { 
  66. let totalOffset = sqrt(xOffset * xOffset + yOffset * yOffset) 
  67. let totalRotation = CGFloat(totalOffset * M_PI / 180.0) 
  68. let xRotationalFactor = CGFloat(totalOffset) / totalRotation 
  69. let yRotationalFactor = CGFloat(totalOffset) / totalRotation 
  70. let currentTransform = CATransform3DTranslate(transformLayer.sublayerTransform, 0.0, 0.0, 0.0) 
  71. let rotationTransform = CATransform3DRotate(transformLayer.sublayerTransform, totalRotation, 
  72. xRotationalFactor * currentTransform.m12 - yRotationalFactor * currentTransform.m11, 
  73. xRotationalFactor * currentTransform.m22 - yRotationalFactor * currentTransform.m21, 
  74. xRotationalFactor * currentTransform.m32 - yRotationalFactor * currentTransform.m31) 
  75. transformLayer.sublayerTransform = rotationTransform 
  76.  
  77. // 5 
  78. override func touchesBegan(touches: NSSet, withEvent event: UIEvent) { 
  79. if let location = touches.anyObject()?.locationInView(someView) { 
  80. for layer in transformLayer.sublayers { 
  81. if let hitLayer = layer.hitTest(location) { 
  82. println("Transform layer tapped!") 
  83. break 
  84.  
  85. override func viewDidLoad() { 
  86. super.viewDidLoad() 
  87.  
  88. // 6 
  89. setUpTransformLayer() 
  90. someView.layer.addSublayer(transformLayer) 
  91.  

上述代碼解釋:

建立屬性,分別爲立方體的邊長、每一個面的顏色,還有一個變換圖層。

建立六個面,旋轉後添加到變換圖層,構成立方體,而後設置變換圖層的z軸錨點,旋轉立方體,將其添加到視圖結構樹。

輔助代碼,用來建立指定顏色的面,還有角度和弧度的轉換。在變換代碼中利用弧度轉換函數在某種程度上能夠增長代碼可讀性。 :]

基於指定xy偏移的旋轉,注意變換應用對象設爲sublayerTransform,即變換圖層的子圖層。

監聽觸摸,遍歷變換圖層的子圖層,對每一個圖層進行點擊測試,一旦成功相應當即跳出循環,不用繼續遍歷。

設置變換圖層,添加到視圖結構樹。

注:currentTransform.m##是啥?問得好,是CATransform3D屬性,表明矩陣元素。想學習如上代碼中的矩陣變換,請參考RW教程組成員Rich Turton的三維變換娛樂教學,還有Mark Pospesel的初識矩陣項目。

在250 x 250的someView視圖中運行上述代碼結果以下:

再試試點擊立方體的任意位置,控制檯會輸出「Transform layer tapped!」信息。

圖層演示應用中能夠調整透明度,此外Bill Dudney軌跡球工具, Swift移植版能夠基於簡單的用戶手勢應用三維變換。

示例 #10:CAEmitterLayer

CAEmitterLayer渲染的動畫粒子是CAEmitterCell實例。CAEmitterLayer和CAEmitterCell都包含可調整渲染頻率、大小、形狀、顏色、速率以及生命週期的屬性。示例以下:

  1. import UIKit 
  2.  
  3. class ViewController: UIViewController { 
  4.  
  5. // 1 
  6. let emitterLayer = CAEmitterLayer() 
  7. let emitterCell = CAEmitterCell() 
  8.  
  9. // 2 
  10. func setUpEmitterLayer() { 
  11. emitterLayer.frame = view.bounds 
  12. emitterLayer.seed = UInt32(NSDate().timeIntervalSince1970) 
  13. emitterLayer.renderMode = kCAEmitterLayerAdditive 
  14. emitterLayer.drawsAsynchronously = true 
  15. setEmitterPosition() 
  16.  
  17. // 3 
  18. func setUpEmitterCell() { 
  19. emitterCell.contents = UIImage(named: "smallStar")?.CGImage 
  20.  
  21. emitterCell.velocity = 50.0 
  22. emitterCell.velocityRange = 500.0 
  23.  
  24. emitterCell.color = UIColor.blackColor().CGColor 
  25. emitterCell.redRange = 1.0 
  26. emitterCell.greenRange = 1.0 
  27. emitterCell.blueRange = 1.0 
  28. emitterCell.alphaRange = 0.0 
  29. emitterCell.redSpeed = 0.0 
  30. emitterCell.greenSpeed = 0.0 
  31. emitterCell.blueSpeed = 0.0 
  32. emitterCell.alphaSpeed = -0.5 
  33.  
  34. let zeroDegreesInRadians = degreesToRadians(0.0) 
  35. emitterCell.spin = degreesToRadians(130.0) 
  36. emitterCell.spinRange = zeroDegreesInRadians 
  37. emitterCell.emissionRange = degreesToRadians(360.0) 
  38.  
  39. emitterCell.lifetime = 1.0 
  40. emitterCell.birthRate = 250.0 
  41. emitterCell.xAcceleration = -800.0 
  42. emitterCell.yAcceleration = 1000.0 
  43.  
  44. // 4 
  45. func setEmitterPosition() { 
  46. emitterLayer.emitterPosition = CGPoint(x: CGRectGetMidX(view.bounds), y: CGRectGetMidY(view.bounds)) 
  47.  
  48. func degreesToRadians(degrees: Double) -> CGFloat { 
  49. return CGFloat(degrees * M_PI / 180.0) 
  50.  
  51. override func viewDidLoad() { 
  52. super.viewDidLoad() 
  53.  
  54. // 5 
  55. setUpEmitterLayer() 
  56. setUpEmitterCell() 
  57. emitterLayer.emitterCells = [emitterCell] 
  58. view.layer.addSublayer(emitterLayer) 
  59.  
  60. // 6 
  61. override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) { 
  62. setEmitterPosition() 
  63.  

以上代碼解析:

1.建立粒子發射器圖層和粒子胞(Creates an emitter layer and cell.)。

2.按照下方步驟設置粒子發射器圖層:

爲隨機數生成器提供種子,隨機調整粒子胞的某些屬性,如速度。

在圖層背景色和邊界之上按renderMode指定的順序渲染粒子胞。

注:渲染模式默認爲無序(unordered),其餘模式包括舊粒子優先(oldest first),新粒子優先(oldest last),按z軸位置從後至前(back to front)還有疊加式渲染(additive)。

因爲粒子發射器須要反覆重繪大量粒子胞,設drawsAsynchronously爲true會提高性能。

而後藉助第四條中會提到的輔助方法設置發射器位置,這個例子有助於理解把drawsAsynchronously設爲true爲什麼可以提高性能和動畫流暢度。

3.這段代碼設了很多東西。

配置粒子胞,設內容爲圖片(圖片在圖層演示項目中)。

指定初速及其變化量範圍(velocityRange),發射器圖層利用上面提到的隨機數種子建立隨機數生成器,在範圍內產生隨機值(初值+/-變化量範圍),其餘以「Range」結尾的相關屬性的隨機化規則相似。

設顏色爲黑色,使自變色(variance)與默認的白色造成對比,白色造成的粒子亮度太高。

利用隨機化範圍設置顏色,指定自變色範圍,顏色速度值表示粒子胞生命週期內顏色變化快慢。

接下來這幾行代碼指定粒子胞分佈範圍,一個全圓錐。設置粒子胞轉速和發射範圍,發射範圍emissionRange屬性的弧度值決定粒子胞分佈空間。

設粒子胞生命週期爲1秒,默認值爲0,表示粒子胞不會出現。birthRate也相似,以秒爲單位,默認值爲0,爲使粒子胞顯示出來,必須設成正數。

最後設xy加速度,這些值會影響已發射粒子的視角。

4.把角度轉換成弧度的輔助方法,還有設置粒子胞位置爲視圖中點。

5.設置發射器圖層和粒子胞,把粒子胞添加到圖層,而後把圖層添加到視圖結構樹。

6.iOS 8的新方法,處理當前設備形態集(trait collection)的變化,好比設備旋轉。不熟悉形態集的話能夠參閱iOS 8教程。

總算說完了!信息量很大,但相信各位聰明的讀者能夠高效吸取。

上述代碼運行效果以下:

圖層演示應用中,你能夠隨意調節不少屬性:

何去何從?

恭喜,看完十則示例和各類圖層子類,CALayer之旅至此告一段落。

但如今纔剛剛開始!新建一個項目,或者打開已有項目,嘗試利用圖層提高性能或營造酷炫效果!實踐出真知。

相關文章
相關標籤/搜索