|     
                        如你所知,我们在iOS应用中看到的都是视图(view),包括按钮视图、表视图、滑动条视图,还有可以容纳其他视图的父视图等。 
                        但你或许不知道在iOS中支撑起每个视图的是一个叫做"图层(layer)"的类,确切地说是CALayer。 
                        本文中您会了解CALayer及其工作原理,还有应用CALayer打造酷炫效果的十则示例,比如绘制矢量图形、渐变色,甚至是粒子系统。 
                        本文要求读者熟悉iOS应用开发和Swift语言的基础知识,包括利用Storyboard构建用户界面。 
                        注:如果您尚未掌握这些基础,不必担心,我们有不少相关教程,例如使用Swift语言编写iOS应用和iOS学徒。 
                        准备开始 
                        要理解图层是什么,最简便的方式就是"实地考察"。我们这就创建一个简单的项目,从头开始玩转图层。 
                        准备好写代码了吗?好!启动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中的代码改写为: 
                        
                           
                             import UIKit    class ViewController: UIViewController {    @IBOutlet weak var viewForLayer: UIView!    var l: CALayer {  return viewForLayer.layer  }    override func viewDidLoad() {  super.viewDidLoad()  setUpLayer()  }    func setUpLayer() {  l.backgroundColor = UIColor.blueColor().CGColor  l.borderWidth = 100.0  l.borderColor = UIColor.redColor().CGColor  l.shadowOpacity = 0.7  l.shadowRadius = 10.0  }    }   | 
                           
                         
                        之前提到iOS中的每个视图都拥有一个关联的图层,你可以通过yourView.layer访问图层。这段代码首先创建了一个叫"l"(小写L)的计算属性,方便访问viewForLayer的图层,可让你少写一些代码。 
                        这段代码还调用了setUpLayer方法设置图层属性:阴影,蓝色背景,红色粗边框。你马上就可以了解这些东西,不过现在还是先构建App,在iOS模拟器中运行(我选了iPhone 
                          6),看看自定义的图层如何。 
                          
                        几行代码,效果还不错吧?还是那句话,每个视图都由图层支撑,所以你也可以对App中的任何视图做出类似修改。我们继续深入。 
                        CALayer基本属性 
                        CALayer有几个属性可以用来自定外观,想想刚才做的: 
                        把图层背景色从默认的无色改为蓝色 
                        通过把边框宽度从默认的0改为100来添加边框 
                        把边框颜色从默认的黑色改为红色 
                        最后把阴影透明度从0(全透明)改为0.7,产生阴影效果,此外还把阴影半径从默认的3改为10。 
                        以上只是CALayer中可以设置的部分属性。我们再试两个,在setUpLayer()中追加以下代码: 
                        
                           
                             l.contents = UIImage(named: "star")?.CGImage  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(_:):  
                        
                           
                             @IBAction func tapGestureRecognized(sender: UITapGestureRecognizer) {  l.shadowOpacity = l.shadowOpacity == 0.7 ? 0.0 : 0.7  }  | 
                           
                          
                        当令视图识别出轻触手势时,代码告知viewForLayer图层在0.7和0之间切换阴影透明度。 
                        你说视图?嗯,没错,重写CALayer的hitTest(_:)也可以实现相同效果,本文后面也会看到这个方法,不过我们这里用的方法也有道理:图层本身并不能响应手势识别,只能响应点击测试,所以我们在视图上设置了轻触手势识别器。 
                        然后如下修改pinchGestureRecognized(_:):  
                        
                           
                             @IBAction func pinchGestureRecognized(sender: UIPinchGestureRecognizer) {  let offset: CGFloat = sender.scale < 1 ? 5.0 : -5.0  let oldFrame = l.frame  let oldOrigin = oldFrame.origin  let newOrigin = CGPoint(x: oldOrigin.x + offset, y: oldOrigin.y + offset)  let newSize = CGSize(width: oldFrame.width + (offset * -2.0), height: oldFrame.height + (offset * -2.0))  let newFrame = CGRect(origin: newOrigin, size: newSize)  if newFrame.width >= 100.0 && newFrame.width <= 300.0 {  l.borderWidth -= offset  l.cornerRadius += (offset / 2.0)  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  let layer = CALayer()  layer.frame = someView.bounds    // 2  layer.contents = UIImage(named: "star")?.CGImage  layer.contentsGravity = kCAGravityCenter    // 3  layer.magnificationFilter = kCAFilterLinear  layer.geometryFlipped = false    // 4  layer.backgroundColor = UIColor(red: 11/255.0, green: 86/255.0, blue: 14/255.0, alpha: 1.0).CGColor  layer.opacity = 1.0  layer.hidden = false  layer.masksToBounds = false    // 5  layer.cornerRadius = 100.0  layer.borderWidth = 12.0  layer.borderColor = UIColor.whiteColor().CGColor    // 6  layer.shadowOpacity = 0.75  layer.shadowOffset = CGSize(width: 0, height: 3)  layer.shadowRadius = 3.0  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的滚动模式可设为水平、垂直或者二维,你也可以用代码命令视图滚动到指定位置:  
                        
                           
                             // In ScrollingView.swift  import UIKit    class ScrollingView: UIView {  // 1  override class func layerClass() -> AnyClass {  return CAScrollLayer.self  }  }    // In CAScrollLayerViewController.swift  import UIKit    class CAScrollLayerViewController: UIViewController {  @IBOutlet weak var scrollingView: ScrollingView!    // 2  var scrollingViewLayer: CAScrollLayer {  return scrollingView.layer as CAScrollLayer  }    override func viewDidLoad() {  super.viewDidLoad()  // 3  scrollingViewLayer.scrollMode = kCAScrollBoth  }    @IBAction func tapRecognized(sender: UITapGestureRecognizer) {  // 4  var newPoint = CGPoint(x: 250, y: 250)  UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: {  [unowned self] in  self.scrollingViewLayer.scrollToPoint(newPoint)  }, completion: nil)  }    }   | 
                           
                          
                        以上代码: 
                        定义一个继承UIView的类,重写layerClass()返回CAScrollLayer,该方法等同于创建一个新图层作为子图层(CALayer示例中做过)。 
                        一个用以方便简化访问自定义视图滚动图层的计算属性。 
                        设滚动模式为二维滚动。 
                        识别出轻触手势时,让滚动图层在UIView动画中滚到新建的点。(注:scrollToPoint(_:)和scrollToRect(_:)不会自动使用动画效果。) 
                        案例研究:如果ScrollingView实例包含大于滚动视图边界的图片视图,在运行上述代码并点击视图时结果如下: 
                         
                          
                        图层演示应用中有可以锁定滚动方向(水平或垂直)的开关。 
                        以下经验规律用于决定是否使用CAScrollLayer: 
                        如果想使用轻量级的对象,只需用代码操作滚动:可以考虑CAScrollLayer。 
                        如果想让用户操作滚动,UIScrollView大概是更好的选择。要了解更多,请参考我们的视频教程。 
                        如果是滚动大型图片:考虑使用CATiledLayer(见后文)。 
                        示例 #3:CATextLayer 
                        CATextLayer能够对普通文本或属性字串进行简单快速的渲染。与UILabel不同,CATextLayer无法指定UIFont,只能使用CTFontRef或CGFontRef。 
                        像下面这样的代码完全可以掌控文本的字体、字体大小、颜色、对齐、折行(wrap)和截断(truncation)规则,也有动画效果: 
                         
                        
                           
                             // 1  let textLayer = CATextLayer()  textLayer.frame = someView.bounds    // 2  var string = ""  for _ in 1...20 {  string += "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce auctor arcu quis velit congue dictum. "  }    textLayer.string = string    // 3  let fontName: CFStringRef = "Noteworthy-Light"  textLayer.font = CTFontCreateWithName(fontName, fontSize, nil)    // 4  textLayer.foregroundColor = UIColor.darkGrayColor().CGColor  textLayer.wrapped = true  textLayer.alignmentMode = kCAAlignmentLeft  textLayer.contentsScale = UIScreen.mainScreen().scale  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),举例如下: 
                         
                           
                             override func viewDidLoad() {  super.viewDidLoad()  // 1  let playerLayer = AVPlayerLayer()  playerLayer.frame = someView.bounds    // 2  let url = NSBundle.mainBundle().URLForResource("someVideo", withExtension: "m4v")  let player = AVPlayer(URL: url)    // 3  player.actionAtItemEnd = .None  playerLayer.player = player  someView.layer.addSublayer(playerLayer)    // 4  NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidReachEndNotificationHandler:",
 name: "AVPlayerItemDidPlayToEndTimeNotification", object: player.currentItem)  }    deinit {  NSNotificationCenter.defaultCenter().removeObserver(self)  }    // 5  @IBAction func playButtonTapped(sender: UIButton) {  if playButton.titleLabel?.text == "Play" {  player.play()  playButton.setTitle("Pause", forState: .Normal)  } else {  player.pause()  playButton.setTitle("Play", forState: .Normal)  }    updatePlayButtonTitle()  updateRateSegmentedControl()  }    // 6  func playerDidReachEndNotificationHandler(notification: NSNotification) {  let playerItem = notification.object as AVPlayerItem  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 
                        绝大多数视频都支持以不同速率正向播放,可以反向播放的视频相对少一些。演示应用也包含了播放控件: 
                          
                         |