CALayer入门教程


本文淘汰.最新文章https://www.jianshu.com/p/b64136e6fc79


我这篇译文为完全翻译,原作者的10个例子我都翻完了.


CALayer Tutorial: Getting Started

大家都清楚,iOS应用里能看到的东西都是view,而实际上view的显示功能是由另一个类CALayer来实现的.这篇文章讲了10个展示CALyer酷炫效果的例子.学习这个教程的前提是你有iOS开发的基础能力,懂swift,用storyboard布置UI.

Getting Started

最快明白layer的方法就是实战.废话不多说,我们建个项目玩一玩layer.
1.启动Xcode(废话)
2.菜单里选择 File\New\Project….如果是刚启动出现在开始界面的话就选择新建一个新项目
3.在对话栏选择 iOS\Application\Single View Application
4.点next,在Product Name输入"CALayerPlayground",organization name 和 identifier 随你来
5.Language选择swift,Devices选Universal
6.保证Core Data的勾没有被打上,选next
7.选个你喜欢的路径放你的项目,然后creat
好,项目创建OK了,接下来要有view
8.在项目导航器里选Main.storyboard
9.把Object LibraryAssistant Editor显示出来,如果已经显示了就跳过这一步
10.选择Editor\Canvas\Show Bounds Rectangles,好让你看到view的边框线
11.从Object Library拖一个view到viewcontroller的scene里,把x和y设为150,宽高设为300
12.选中这个view,给这个view添加Horizontal Center in ContainerVertical Center in Container这两个约束,在Xcode底部的align按钮里操作.
13.同样点击底部的pin按钮,把宽高设为300
14.最后通过control加拖view的方式把view连接到ViewController.swift文件中,outlet起名为viewForlayer
好了,Xcode现在应该是酱紫的

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
  } 
}

前面说的每个view都有一个layer,你能用yourView.layer来访问layer.这段代码做的第一件事就是创建了一个可以用来访问viewForLayer的layer的属性,这个属性名称是小写l,紧接着的代码就用到了这个属性的内容.
这段代码还调用了setUpLayer方法来设置layer的几个属性,比如阴影,背景色,还有线框色.等下你就会明白setUpLayer()方法.不过现在还是运行一下程序来看看你的自定义layer.
[图片上传失败...(image-8d1b48-1587374045880)]
才几行代码就有折么酷炫的效果.每一个view都可以做这样的事,只要它有layer.接下来仔细看看.

CALayer的基础属性

CALayer有几个可以改变它外观的属性,回想一下你做过哪些
1.把layer的背景色从默认的没有颜色改成蓝的
2.把它的线宽从默认的0改成了100
3.把它的颜色从黑色改成了红色
4.最后还是改了阴影,把不透明度从默认的0改到了0.7,这样就显示了阴影.然后又把阴影半径从默认的3改到了10.
你能设置CALayer的几个属性.来,多试两个.把下面两行代码加到setUpLayer()方法的底部:

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

content属性能让你给layer的内容物设置一张图片,所以你是放了一张名称是star的图片在这里.当然,首先你得把图片放到项目里.[图片上传失败...(image-a05a0c-1587374045880)]
把它放到项目,运行看一下效果
[图片上传失败...(image-1e6b7a-1587374045880)]
看看图片为什么是居中的,因为把contentsGravity属性设置为kCAGravityCenter了.你没想错,你也可以设为居上下左右,左上左下等等.

改变Layer的外观

我们来玩一下,加几个手势识别器来控制layer的外观.在Xcode里,拖一个点击手势识别器到viewForLayer对象上.参考一下,下面的图片是拖手势识别器到对象上看起来的样子:
[图片上传失败...(image-e60a52-1587374045880)]

提示:如果对手势识别器不熟悉,可以看下这篇文章Using UIGestureRecognizer with Swift.

重复上面的步骤,再加一个缩放手势.

通过control加拖拽的方式把storyboard顶部的两个手势识别器拖到ViewController.swift里去,把它们加到setUpLayer()方法下面.
[图片上传失败...(image-d92efa-1587374045880)]

把tapGestureRecognized(_:)改成这样:

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

这段代码的意思是让layer根据点击手势来让它的不透明度在0和0.7之间变动.
当然你也可以重写hitTest(_:)方法达到同样的效果,等下我们就会用到这个方法.我们来理解一下上面方法的逻辑,layer能做的只有hit testing,它与手势识别互动不了.所以我们是把手势识别器加到view上面.

现在来把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 }
}

这段代码根据缩放手势来生成正的或者负的位移,调整layer的frame,边线的宽度和圆角.
layer的圆角默认都是0,也就是个矩形.增加圆角值能生成圆角,当圆角值是layer宽度的一半就会变成一个圆了.
设置圆角不会剪切layer的内容,只有把masksToBounds设为true才会剪切.
运行看一下吧:
[图片上传失败...(image-108568-1587374045880)]

The Great CALayer Tour

[图片上传失败...(image-6090d9-1587374045880)]

CALayer不是只有你想的几个属性和方法,它还有几个拥有独一无二的属性和方法的子类.
有比guided tour更好的了解这些API的途径吗,raywenderlich.com-style?
对于接下来的文章,你需要两样东西:
The Layer Player App
The Layer Player Source Code

这个handy app有十个不同的等下你会学习到的CALayer类型的样例,来看看大概是神马样子的:
[图片上传失败...(image-db9625-1587374045880)]

每当学到下面的某个样例,我建议你玩一下这个CALayer app,随意地看一下我给你的源码.不要慌,你不用把所有代码都码下来,放轻松慢慢看.
这些样例应该是很不错的学习不同CALayer的途径,我们希望你能够喜欢.

Example #1: CALayer

前面已经讲了怎样使用CALayer类.
下面是另外几个没讲到的属性
****1.layer可以拥有子layer.**** 就像view可以拥有子view.你能拿来做很多酷炫效果.
****2.layer属性是具有动画性的.**** 修改layer的属性能生成随时间变化的动画效果.也可以自定义动画的时间.
****3.layer是轻量级的.**** layer要比view轻量,因此layer能达到更高效的性能.
****4.layer有很多有用的属性.**** 你已经看了很多了,接下来再看看其他的吧.

前面你已经看到了,layer有很多有用的属性.我们来看下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
layer.shouldRasterize = true
someView.layer.addSublayer(layer)

1.创建一个CALayer的实例,把someView的bounds设给它.
2.居中设置一张图片给这个layer.注意.CGImage对象是assigned的.
3.使用contentsGravity会使用这个放大滤镜,能够改变所有的size和position.如果geometryFlipped没有设为true,坐标系和阴影会反转.
4.背景色设为绿色,将layer设为可见, masksToBounds设为false,也就是当layer的size比contents得size要小时,不会裁剪contents.
5.圆角值设为宽度的一半,那么视觉上就会成为一个圆.注意,layer的颜色是指定为CGColor类型的.
6.创建了阴影效果,把shouldRasterize开启为true(下面会讲到).(译者注:原文代码里并没有layer.shouldRasterize = true这句代码,我加了进去)

嗯,来看一下效果:
[图片上传失败...(image-c5cf4a-1587374045880)]

CALayer有两个提高性能的属性,分别是 shouldRasterize 和 drawsAsynchronously.

shouldRasterize默认为false.设为true能提高性能的原因是layer的contents只会被渲染一次.这个方法对于那些只是用来在屏幕上做动画,但是外观不会变的对象有奇效.
drawsAsynchronously则与shouldRasterize完全相反.它默认也是false.如果某个对象需要重复地被绘制,那么开启true就能提高性能.比如例子发射layer,它会持续不断地渲染动画性的细小的图像(译者:可以想象一下雨雪,火焰这样的东西).(等下CAEmitterLayer会讲到.)
提醒一句:使用这两个属性时想一下可能的后果.对比一下开合没开的性能.当没开时,性能可能低得一塌糊涂.

接下来把注意力转移到 Layer Player.它集合了对layer属性的很多控制:
[图片上传失败...(image-1d1631-1587374045880)]

注意:在CALayerPlayground 项目里你了解到,layer不是响应链里的一员,所以它对触摸和手势不能做出反应.
但是你可以CATransformLayer的样例里看到我们可以hit test他们.在CAReplicatorLayer样例又能看到添加自定义动画效果

xample #2: CAScrollLayer

CAScrollLayer显示一个可滚动layer的一部分.CAScrollLayer相当的基础,它不能对触摸做出反应,甚至查看这个可滚动layer的bounds,所以它只能防止滚动超出范围.
UIScrollView并没有使用CAScrollLayer来实现自己的滚动功能,UIScrollView实际上是通过修改layer的bounds来达到滚动效果的.
对于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)
 } 
}

上面代码:
1.写了一个UIView的子类,重写了layerClass()方法,用来返回CAScrollLayer.这是个不常规的用来生成一个新layer并添加为子layer的方法.就像CALayer样例里那样.
2.设置一个属性,能配合自定义UIView的滚动视图的layer进行高效工作.
3.滚动模式设为垂直水平都可以.
4.点击时,生成一个点,这个滚动layer就通过会动画滚动到这个点的位置.单单scrollToPoint(:)
和scrollToRect(
:)并不会生成动画

一个ScrollingView持有一张比本身的bounds大的图片.运行程序,点击视图就会有下面一样的效果:
[图片上传失败...(image-74955d-1587374045880)]

Layer Player有锁定滚动方向的功能.
下面是是CAScrollLayer的一些使用Tips:
1.当你需要一个轻量的只需要程序来控制滚动的东西时,考虑一下CAScrollLayer.
2.如果你想用户来操控滚动时,那么就用UIScrollView.这是关于UIScrollView的教程.上干货
3.如果要滚动超级无敌大的图片,还是考虑用CATiledLayer吧.下面有讲哦.

Example #3: CATextLayer

CATextLayer能够快速高效简单地来渲染纯文本或者attributed strings.不像UILable,CATextLayer没有指定的UIFont,只有一个CTFontRef 或者 CGFontRef.

一段这样的代码能该改变字体,字体大小,颜色,对齐方式等等:

// 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)

解释下上面代码:
1.创建一个CATextLayer实例,bounds设为someView的bounds.
2.创建一段重复地文本,赋给这个textlayer
3.创建一个字体,赋给textlayer
4.设置文本环绕并且左对齐(你可以设为自然,右对齐.中心对齐,或者你觉得合理的).把它的contentsScale设为跟屏幕一样.然后添加到layer的继承树里.

所有的layer类,包括CATextLayer,都是在默认1的缩放比例下渲染的.当本来就是属于某个视图里时,layer会自动把他们的contentsScale调整到对当前屏幕来说合理的缩放比例.手动创建的layer需要你显示地调整contentsScale,否则缩放比例一直是1,在视网膜屏看起来就会模糊蛋疼.

如果添加到了一个方形的someView,那么生成的文本是这个样子的:
[图片上传失败...(image-6f9b48-1587374045880)]

高兴得时候你可以用省略号来代表被剪切的文本.(截断)Truncation默认是没有的,你可以设在开头,中间,或者结尾.看下面示意图:
[图片上传失败...(image-a12b3b-1587374045880)]
[图片上传失败...(image-ece582-1587374045880)]
[图片上传失败...(image-fe6061-1587374045880)]

Layer Player有许多能操作CATextLayer属性的控制功能:
[图片上传失败...(image-9b6cd9-1587374045880)]

Example #4: AVPlayerLayer

AVPlayerLayer有一个用来播放av多媒体文件的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      
 AVPlayerItemplayerItem.seekToTime(kCMTimeZero)

}

代码分析:
1.创建新的player layer,设置frame
2.用一个AV资源创建一个player
3.告诉player结束时神马都不做.可选的操作有:如暂停播放下一个项目
4.注册AVPlayer当播放结束时的通知.(销毁时移除监听通知)
5.播放按钮点下时,触发播放控制并设置按钮的标题.

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px 'Lucida Grande'; color: #4e4e4e; -webkit-text-stroke: #4e4e4e}span.s1 {font-kerning: none}

AVPlayerLayer 和其上面创建的 AVPlayer 会直观地表示 AVPlayerItem 实例的第一个帧:
[图片上传失败...(image-ff4069-1587374045880)]
AVPlayerLayer有一对额外的属性:

videoGravity :设置视频显示的大小调整行为
videoGravity :确认视频是否可以播放

AVPlayer,另一面它有不少额外的属性和方法.一个就是播放速率,从0到1的速度.0是暂停,1是正常(1x).
暂停就是把播放速率设为0,播放就是设为1.那么快进呢,慢动作呢,循环播放呢.AVPlayerLayer都已经想到了.设为2就是2倍快进,负当然是慢动作了.
当不以正常速度播放时,最好先确认一下AVPlayerItem可以以一下速率播放:
canPlayFastForward()

canPlaySlowForward()

canPlayReverse()

canPlaySlowReverse()

canPlayFastReverse()

大多数视频都可以快进,但是可以倒退播放的视频不多见.Layer Player当然也有播放可控制功能:
[图片上传失败...(image-7dbf1c-1587374045880)]

Example #5: CAGradientLayer

CAGradientLayer能轻易地混合多种颜色,所以很适合用来当背景.如何设置呢,你给他一个CGColor的数组,还有一个开始点和结束点.
记住,开始点和结束点不是清楚的点.他们定义在layer的bounds中.x为1表示在这个layer的右边缘上,y是1的话就说明在layer的底部线上.
CAGradientLayer有个type属性,虽然只有一个kCAGradientLayerAxial选项,它线性地转换数组里的颜色.
这表示如果你在开始点和结束点中间画了条线,渐进性会出现在A线,而B线上的颜色都是一样的:
[图片上传失败...(image-75b3f3-1587374045880)]

你可以通过一个值介于0到1的数组控制locations属性.

下面是创建gradient layer的代码:

let gradientLayer = CAGradientLayer()
gradientLayer.frame = someView.bounds
gradientLayer.colors = [cgColorForRed(209.0, green: 0.0, blue: 0.0), 
cgColorForRed(255.0, green: 102.0, blue: 34.0), 
cgColorForRed(255.0, green: 218.0, blue: 33.0),
cgColorForRed(51.0, green: 221.0, blue: 0.0), 
cgColorForRed(17.0, green: 51.0, blue: 204.0), 
cgColorForRed(34.0, green: 0.0, blue: 102.0),
 cgColorForRed(51.0, green: 0.0, blue: 68.0)]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 0, y: 1)
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
}

上面的代码创建了一个gradient layer,将layer的frame匹配someView的bounds.指定一组颜色,设置开始点和结束点,把layer添加到视图的layer上,最后长这个样子:
[图片上传失败...(image-db54bd-1587374045880)]

叼炸天!

Layer Player也有这些功能哦:
[图片上传失败...(image-a8c7dc-1587374045880)]

Example #6: CAReplicatorLayer

CAReplicatorLayer能复制一个layer指定的次数,又能做出酷炫的效果了.
每一个layer拷贝可以有他自己的位置和颜色变化,可以推迟其绘图动画效果给整体复制器层。此外可以保存深度,使复制层具有 3D 效果。下面是一个示例 ︰

// 1
let replicatorLayer = CAReplicatorLayer()
replicatorLayer.frame = someView.bounds 
// 2
replicatorLayer.instanceCount = 30
replicatorLayer.instanceDelay = CFTimeInterval(1 / 30.0)
replicatorLayer.preservesDepth = false
replicatorLayer.instanceColor = UIColor.whiteColor().CGColor 

// 3
replicatorLayer.instanceRedOffset = 0.0
replicatorLayer.instanceGreenOffset = -0.5
replicatorLayer.instanceBlueOffset = -0.5
replicatorLayer.instanceAlphaOffset = 0.0 

// 4
let angle = Float(M_PI * 2.0) / 30
replicatorLayer.instanceTransform=CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)
someView.layer.addSublayer(replicatorLayer) 

// 5
let instanceLayer = CALayer()
let layerWidth: CGFloat = 10.0
let midX = CGRectGetMidX(someView.bounds) - layerWidth / 2.0
instanceLayer.frame = CGRect(x: midX, y: 0.0, width: layerWidth, height:     layerWidth * 3.0)
instanceLayer.backgroundColor =UIColor.whiteColor().CGColor
replicatorLayer.addSublayer(instanceLayer) 

// 6
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.fromValue = 1.0
fadeAnimation.toValue = 0.0
fadeAnimation.duration = 1
fadeAnimation.repeatCount = Float(Int.max) 

// 7
instanceLayer.opacity = 0.0
instanceLayer.addAnimation(fadeAnimation, forKey: "FadeAnimation")

解释下:
1.创建一个CAReplicatorLayer实例,把bounds与someView的匹配.
2.设置这个重复器layer拷贝的数量(instanceCount),然后延迟绘制.设这个重复器layer的深度为2D(preservesDepth = false),设置拷贝的颜色为白色
3.给每个连续被拷贝的实例的RGB颜色的值添加offsets值.每个颜色offset值默认是0,添加了之后就会影响每一个实例.这个例子当中,实例起初就被设为白色,所以红,绿,蓝的值都是1.之后红的又被改成了0,绿和蓝被改成了负数,这样会让红色成为一个亮眼的颜色.同样的,透明度也加到了每个连续的被拷贝对象上.
4.把所有被复制实例旋转变换成一个圆
5.创建一个layer,bounds改成能让第一个被复制的对象居于中上方的位置,好放下组成的整个圆
6.做一个透明度从0到1的渐变动画
7.把后来创建的layer的不可见度调到0

[图片上传失败...(image-aeca2-1587374045880)]

[图片上传失败...(image-96c8fe-1587374045880)]

Example #7: CATiledLayer

CATiledLayer异步地一块一块地绘制内容.对超级大的图像或者很多内容在某个时刻只看它一小块的情况很有效果.不用每次看的时候就立马把整个内容加载到内存里,每次加载一部分.

有很多绘制的方法.一种就是是重写UIView,用CATiledLayer重复地绘制一块一块的内容,然后填充到背景里,像这样:

// In ViewController.swift
import UIKit 
class ViewController: UIViewController {  

// 1 
@IBOutlet weak var tiledBackgroundView:
 TiledBackgroundView! 
} 

// In TiledBackgroundView.swift
import UIKit 
class TiledBackgroundView: UIView {
  let sideLength = CGFloat(50.0)  

// 2
 override class func layerClass() -> AnyClass { 
      return CATiledLayer.self
 } 

 // 3
 required init(coder aDecoder: NSCoder) {
   super.init(coder: aDecoder) 
   srand48(Int(NSDate().timeIntervalSince1970))
   let layer = self.layer as CATiledLayer
   let scale = UIScreen.mainScreen().scale
   layer.contentsScale = scale
   layer.tileSize = CGSize(width: sideLength * scale, height: sideLength *   scale)
 } 

 // 4 
override func drawRect(rect: CGRect) { 
   let context = UIGraphicsGetCurrentContext()
   var red = CGFloat(drand48()) 
   var green = CGFloat(drand48())
   var blue = CGFloat(drand48()) 
  CGContextSetRGBFillColor(context, red, green, blue, 1.0)     CGContextFillRect(context, rect)
 } 
}

来看一下:
1.tiledBackgroundView放在点(150.150),宽高都是300.
2.layerClass()被重写了,这样这个view的layer就会变成CATiledLayer类
3.rand48()用来在drawRect()里生成随机色,把scales与屏幕匹配,并设置绘制块的大小
4.重写drawRect(),用随机色填充背景
最后代码绘制一个6x6的颜色方块:
[图片上传失败...(image-217d0d-1587374045880)]

Layer Player还在上面画了一条路径:
[图片上传失败...(image-140437-1587374045881)]

放大上面的星星,你会发现细节处变得模糊了:
[图片上传失败...(image-a37087-1587374045881)]

模糊的原因是layer保持的细节量(levelsOfDetail)不高.CATiledLayer有两个相关的属性,levelsOfDetail 和 levelsOfDetailBias.
levelsOfDetail,表示所持有的细节量.一般默认是1,最大值就是最底层每个细节都有一个像素.
levelsOfDetailBias,是这个layer缓存的放大级别的细节的数量.默认0.
举个栗子,把上面的例子的levelsOfDetailBias弄成5,会把缓存等级提高到2x, 4x, 8x, 16x 和32x,缩放后看起来像这样:
[图片上传失败...(image-150bfd-1587374045881)]

接下来讲一下CATiledLayer对于滚动超大图片的用处.

虽然你需要给这个layer提供绘制方块,和当用户拖动时哪块方块抓住的逻辑,感觉很麻烦.但是它带来的性能提升是非常大的.
Layer Player的UIImage+TileCutter.swift里有一个UIImage extension..它的工作是把源图片切割成特定大小的方块,然后根据行和列起名.比如下面那个清晰的方块就是3行7列:
[图片上传失败...(image-4777bb-1587374045881)]

通过放置这些方块就可以创建这个方块layer了:

import UIKit 
class TilingViewForImage: UIView { 

   // 1
   let sideLength = CGFloat(640.0) 
  let fileName = "windingRoad"
   let cachesPath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as String  

// 2
 override class func layerClass() -> AnyClass {
   return CATiledLayer.self 
}  

// 3 
required init(coder aDecoder: NSCoder) { 
  super.init(coder: aDecoder)
   let layer = self.layer as CATiledLayer
   layer.tileSize = CGSize(width: sideLength, height: sideLength)
 }  
// 4
 override func drawRect(rect: CGRect) {
  let firstColumn = Int(CGRectGetMinX(rect) / sideLength)
  let lastColumn = Int(CGRectGetMaxX(rect) / sideLength) 
  let firstRow = Int(CGRectGetMinY(rect) / sideLength) 
  let lastRow = Int(CGRectGetMaxY(rect) / sideLength) 
   for row in firstRow...lastRow { 
      for column in firstColumn...lastColumn {
         if let tile = imageForTileAtColumn(column, row: row) {

         let x = sideLength * CGFloat(column)
         let y = sideLength * CGFloat(row)
         let point = CGPoint(x: x, y: y)
         let size = CGSize(width: sideLength, height: sideLength)
         var tileRect = CGRect(origin: point, size: size)
         tileRect = CGRectIntersection(bounds, tileRect) 
        tile.drawInRect(tileRect) 
      } 
    } 
  }
 }  
func imageForTileAtColumn(column: Int, row: Int) -> UIImage? {
  let filePath = "\(cachesPath)/\(fileName)_\(column)_\(row)" 
  return UIImage(contentsOfFile: filePath) 
  } 
}

上面代码:
1.创建边长,图片的文件名和方块切割的缓存的路径这三个属性.
2.重写 layerClass()返回 CATiledLayer.
3.实现init(_:)
4.重写drawRect(),让它根据行列位置进行绘制

然后把该子类的视图的大小调到图片大小,添加到一个scrollview:
[图片上传失败...(image-efdf6-1587374045881)]
然后你就有了一个大图像的平滑滚动了:
[图片上传失败...(image-9ff033-1587374045881)]
快速拖动时,你能明显地看到是一块一块出现的.要减弱这个效果的话,可以把绘制块的大小变小,创建一个CATiledLayer子类,重写fadeDuration()方法返回0:

class TiledLayer: CATiledLayer {  
  override class func fadeDuration() -> CFTimeInterval {
     return 0.0
   }
 }

Example #8: CAShapeLayer

CAShapeLayer利用可伸缩矢量路径进行绘制,它比使用图片快得多。更好的是,你不用再提供图片的正常 @2x @3x版本了.
另外,你有各种各样的属性用来定义自定义线的粗细,颜色,如何加入其他的线,如果线相交形成一个封闭的区域,应填什么颜色。下面是一个例子:

import UIKit 
class ViewController: UIViewController { 
   @IBOutlet weak var someView: UIView!  

    // 1
     let rwColor = UIColor(red: 11/255.0, green: 86/255.0, blue:   14/255.0, alpha: 1.0)
     let rwPath = UIBezierPath() 
     let rwLayer = CAShapeLayer() 

     // 2
    func setUpRWPath() {
    rwPath.moveToPoint(CGPointMake(0.22, 124.79))           
    rwPath.addLineToPoint(CGPointMake(0.22, 249.57))   
    rwPath.addLineToPoint(CGPointMake(124.89, 249.57)) 
    rwPath.addLineToPoint(CGPointMake(249.57, 249.57)) 
    rwPath.addLineToPoint(CGPointMake(249.57, 143.79))   
    rwPath.addCurveToPoint(CGPointMake(249.37, 38.25), controlPoint1: CGPointMake(249.57, 85.64), controlPoint2: CGPointMake(249.47, 38.15)) 
    rwPath.addCurveToPoint(CGPointMake(206.47, 112.47), controlPoint1: CGPointMake(249.27, 38.35), controlPoint2: CGPointMake(229.94, 71.76)) 
    rwPath.addCurveToPoint(CGPointMake(163.46, 186.84), controlPoint1: CGPointMake(182.99, 153.19), controlPoint2: CGPointMake(163.61, 186.65)) 
    rwPath.addCurveToPoint(CGPointMake(146.17, 156.99), controlPoint1: CGPointMake(163.27, 187.03), controlPoint2: CGPointMake(155.48, 173.59))
    rwPath.addCurveToPoint(CGPointMake(128.79, 127.08), controlPoint1: CGPointMake(136.82, 140.43), controlPoint2: CGPointMake(129.03, 126.94)) 
    rwPath.addCurveToPoint(CGPointMake(109.31, 157.77), controlPoint1: CGPointMake(128.59, 127.18), controlPoint2: CGPointMake(119.83, 141.01)) 
    rwPath.addCurveToPoint(CGPointMake(89.83, 187.86), controlPoint1: CGPointMake(98.79, 174.52), controlPoint2: CGPointMake(90.02, 188.06)) 
    rwPath.addCurveToPoint(CGPointMake(56.52, 108.28), controlPoint1: CGPointMake(89.24, 187.23), controlPoint2: CGPointMake(56.56, 109.11)) 
    rwPath.addCurveToPoint(CGPointMake(64.02, 102.25), controlPoint1: CGPointMake(56.47, 107.75), controlPoint2: CGPointMake(59.24, 105.56)) 
    rwPath.addCurveToPoint(CGPointMake(101.42, 67.57), controlPoint1: CGPointMake(81.99, 89.78), controlPoint2: CGPointMake(93.92, 78.72)) 
    rwPath.addCurveToPoint(CGPointMake(108.38, 30.65), controlPoint1: CGPointMake(110.28, 54.47), controlPoint2: CGPointMake(113.01, 39.96))
    rwPath.addCurveToPoint(CGPointMake(10.35, 0.41), controlPoint1: CGPointMake(99.66, 13.17), controlPoint2: CGPointMake(64.11, 2.16)) 
    rwPath.addLineToPoint(CGPointMake(0.22, 0.07)) 
    rwPath.addLineToPoint(CGPointMake(0.22, 124.79)) 
    rwPath.closePath() }  

// 3 
   func setUpRWLayer() { 
    rwLayer.path = rwPath.CGPath
    rwLayer.fillColor = rwColor.CGColor
    rwLayer.fillRule = kCAFillRuleNonZero
    rwLayer.lineCap = kCALineCapButt 
    rwLayer.lineDashPattern = nil 
    rwLayer.lineDashPhase = 0.0 
    rwLayer.lineJoin = kCALineJoinMiter 
    rwLayer.lineWidth = 1.0 
    rwLayer.miterLimit = 10.0 
    rwLayer.strokeColor = rwColor.CGColor
   }  
    override func viewDidLoad() {
       super.viewDidLoad()  

    // 4 
        setUpRWPath() 
        setUpRWLayer() 
        someView.layer.addSublayer(rwLayer) 
    }
}

解释代码:
1.创建颜色路径shapelayer对象
2.绘制路径
3.设置layer,它的路径被设为第二步的路径,填充色设为第一步里设置的颜色,填充规则被显式地设置为非零的默认值。
唯一的其他选择是even-odd,并且对于这个形状,没有交叉的路径填充规则让差异变得很小。
非零规则把从左至右的路径记为+ 1从右至左的路径为1,它总和了所有的路径,如果和大于0,那他就根据路径填充颜色.
从本质上讲,非零填充这个形状的所有点。
even-odd规则计数形成一个形状的路径的总数,如果最后是奇数,则该形状被填充。当一个图片是一千字时,这绝对是个列子。
形成这个五角星的路径交点的总数是偶数,所以没有被填充.而形成每个三角形的路径的交点是奇数,所以该三角形被填充。
[图片上传失败...(image-68bbe5-1587374045881)]


[图片上传失败...(image-6f229b-1587374045881)]


我们跳过了Layer Player里的下一个demo.因为CAEAGLLayer已经被CAMetalLayer替代了.这里有相关教程.

Example #9: CATransformLayer

CATransformLayer不平铺它的子layer,它很适合3D结构的视图.CATransformLayer实际是子layer的一个容器,每个子layer有自己的形变和透明度,但,类似线框宽度和颜色的layer属性会被忽略掉.
因为CATransformLayer没有2维坐标系,所以没办法检测点击.但是在独立的子layer里可以检测到.举个栗子:

import UIKit
 class ViewController: UIViewController { 
 @IBOutlet weak var someView: UIView!  

// 1 
let sideLength = CGFloat(160.0) 
var redColor = UIColor.redColor()
 var orangeColor = UIColor.orangeColor() 
var yellowColor = UIColor.yellowColor() 
var greenColor = UIColor.greenColor() 
var blueColor = UIColor.blueColor() 
var purpleColor = UIColor.purpleColor() 
var transformLayer = CATransformLayer()  

// 2 
func setUpTransformLayer() { 
var layer = sideLayerWithColor(redColor)         
transformLayer.addSublayer(layer) 

 layer = sideLayerWithColor(orangeColor) 
var transform = CATransform3DMakeTranslation(sideLength / 2.0, 0.0, sideLength / -2.0)
 transform = CATransform3DRotate(transform, degreesToRadians(90.0), 0.0, 1.0, 0.0) 
layer.transform = transform 
transformLayer.addSublayer(layer)  
layer = sideLayerWithColor(yellowColor) 
layer.transform = CATransform3DMakeTranslation(0.0, 0.0, -sideLength) 
transformLayer.addSublayer(layer)  
layer = sideLayerWithColor(greenColor) 
transform = CATransform3DMakeTranslation(sideLength / -2.0, 0.0, sideLength / -2.0)
transform = CATransform3DRotate(transform, degreesToRadians(90.0),   0.0, 1.0, 0.0) 
layer.transform = transform
   transformLayer.addSublayer(layer)  
layer = sideLayerWithColor(blueColor)
 transform = CATransform3DMakeTranslation(0.0, sideLength / -2.0,       sideLength / -2.0) 
transform = CATransform3DRotate(transform, degreesToRadians(90.0), 1.0, 0.0, 0.0)
 layer.transform = transform
 transformLayer.addSublayer(layer) 
 layer = sideLayerWithColor(purpleColor) 
transform = CATransform3DMakeTranslation(0.0, sideLength / 2.0, sideLength / -2.0) 
transform = CATransform3DRotate(transform,     degreesToRadians(90.0), 1.0, 0.0, 0.0) 
layer.transform = transform 
transformLayer.addSublayer(layer) 
 transformLayer.anchorPointZ = sideLength / -2.0 
 applyRotationForXOffset(16.0, yOffset: 16.0) 

}  

// 3
 func sideLayerWithColor(color: UIColor) -> CALayer { 
let layer = CALayer() 
layer.frame = CGRect(origin: CGPointZero, size: CGSize(width:     sideLength, height: sideLength))
 layer.position = CGPoint(x: CGRectGetMidX(someView.bounds), y: CGRectGetMidY(someView.bounds)) 
layer.backgroundColor = color.CGColor return layer } 
 func degreesToRadians(degrees: Double) -> CGFloat { 
return CGFloat(degrees * M_PI / 180.0)
 } 

 // 4 
func applyRotationForXOffset(xOffset: Double, yOffset: Double) {

 let totalOffset = sqrt(xOffset * xOffset + yOffset * yOffset)
 let totalRotation = CGFloat(totalOffset * M_PI / 180.0) let     xRotationalFactor = CGFloat(totalOffset) / totalRotation
 let yRotationalFactor = CGFloat(totalOffset) / totalRotation 

 let currentTransform = CATransform3DTranslate(transformLayer.sublayerTransform, 0.0, 0.0, 0.0)
 let rotationTransform = CATransform3DRotate(transformLayer.sublayerTransform, totalRotation, xRotationalFactor * currentTransform.m12 - yRotationalFactor * currentTransform.m11, xRotationalFactor * currentTransform.m22 - yRotationalFactor * currentTransform.m21, xRotationalFactor * currentTransform.m32 - yRotationalFactor * currentTransform.m31) 

transformLayer.sublayerTransform = rotationTransform 

}  

// 5
 override func touchesBegan(touches: NSSet, withEvent event: UIEvent)               {
 if let location = touches.anyObject()?.locationInView(someView) { 
  for layer in transformLayer.sublayers {
      if  let hitLayer = layer.hitTest(location) {
          println("Transform layer tapped!") break } 
      } 
   } 
}  
override func viewDidLoad() {
 super.viewDidLoad()  
// 6 
setUpTransformLayer()
 someView.layer.addSublayer(transformLayer)
 } 
}

解释代码:
1.为边长,颜色,方块的每个面创建属性和一个transform layer
2.通过创建旋转面并添加到这个transform layer来产生方块.然后设置transform layerz轴的锚点,旋转方块,添加到视图继承树里.
3.创建帮助器代码来用指定的颜色创建每个方块面layer并将度转换为弧度。为什么是弧度?因为我觉得它更直观。
4.应用一个基于x和y偏移的旋转.注意看下,代码把变形设到了子layerTransform上,而且应用于transform layer的子layer。
5.检查transform layer里的触摸.
6.设置并添加到view里

note:currentTransform.m##这些是什么?这些都是矩阵代表包括矩形数组的行和列的元素的 CATransform3D 属性.想了解更多的话点我.

运行程序,看下这个250x250的view:
[图片上传失败...(image-ec6bf5-1587374045881)]

Layer Player :
[图片上传失败...(image-7ce231-1587374045881)]

Example #10: CAEmitterLayer

总算到最后一个了.

CAEmitterLayer 渲染 CAEmitterCell 实例的具有动画性的粒子。CAEmitterLayer 和 CAEmitterCell 有更改渲染速率、 大小、 形状、 颜色、 速度、 生命周期和更多的属性。
栗子:

  import UIKit 
  class ViewController: UIViewController { 

 // 1
         let emitterLayer = CAEmitterLayer() 
         let emitterCell = CAEmitterCell()  

 // 2
 func setUpEmitterLayer() { 
          emitterLayer.frame = view.bounds 
          emitterLayer.seed = UInt32(NSDate().timeIntervalSince1970)                 
          emitterLayer.renderMode = kCAEmitterLayerAdditive   
          emitterLayer.drawsAsynchronously = true 
          setEmitterPosition() 

        }  

    // 3 
  func setUpEmitterCell() { 
        emitterCell.contents = UIImage(named: "smallStar")?.CGImage  

        emitterCell.velocity = 50.0 
        emitterCell.velocityRange = 500.0  

        emitterCell.color = UIColor.blackColor().CGColor     
        emitterCell.redRange = 1.0
        emitterCell.greenRange = 1.0 
        emitterCell.blueRange = 1.0 
        emitterCell.alphaRange = 0.0 
        emitterCell.redSpeed = 0.0 
        emitterCell.greenSpeed = 0.0 
        emitterCell.blueSpeed = 0.0 
        emitterCell.alphaSpeed = -0.5 

        let zeroDegreesInRadians = degreesToRadians(0.0)       
        emitterCell.spin = degreesToRadians(130.0)     
        emitterCell.spinRange = zeroDegreesInRadians   
        emitterCell.emissionRange = degreesToRadians(360.0)  


        emitterCell.lifetime = 1.0 
        emitterCell.birthRate = 250.0 
        emitterCell.xAcceleration = -800.0 
        emitterCell.yAcceleration = 1000.0 
  }  

// 4 
 func setEmitterPosition() { 
     emitterLayer.emitterPosition = CGPoint(x:CGRectGetMidX(view.bounds), y: CGRectGetMidY(view.bounds))       
    } 

 func degreesToRadians(degrees: Double) -> CGFloat { 
                  return CGFloat(degrees * M_PI / 180.0) 
     } 
 override func viewDidLoad() {
   super.viewDidLoad() 

   // 5 
  setUpEmitterLayer() 
  setUpEmitterCell()
  emitterLayer.emitterCells = [emitterCell]            
  view.layer.addSublayer(emitterLayer) }  

  // 6 
  
    override func traitCollectionDidChange(previousTraitCollection:UITraitCollection?) { 
          setEmitterPosition() 
   } 
}

解释代码:
1.创建 emitter layer 和cell
2.通过以下操作设置emitter layer

1. 为图层用来给cell的属性(比如速度)值随机化的随机数发生器提供了一个seed。下一条进一步解释了这个问题。
2. 上面的渲染粒子cell(Renders emitter cell),他们的背景色 ,线宽是根据renderMode按顺序确定的.
   注意点---苹果文档目前不正确地指出,此属性的值定义在Emitter Modes模式下。事实上,renderMode定义在Emitter Render Order下。默认值是unordered, 其他的值有oldest first(最老的优先), oldest last, back to front 和 additive.
3.drawsAsynchronously设为true.这个操作能够提高性能,发射器是持续不断地重绘它的cell的.
4.通过setEmitterPosition()来设置emitter的位置

3.这一块代码有很多操作:

  1.设置emitter cell的内容为一张图片(这张图片Layer Player里有)
  2.然后它指定初始速度和最大的方差 (velocityRange).emitter layer使用上面的种子创建了一个在这个范围内随机生成值的随机数发生器.
  3.把颜色设置为黑色,允许从默认的白色开始变化,因为白色太过于明亮。
  4.下一步是用相同的随机器设置一系列颜色范围,这一次是把变化的范围指定给每个颜色.速度表示颜色变化得有多快.
  5.设置cell的旋转速率和发射范围.发射范围决定了emitter cells如何在给定的emissionRange里分布
  6.cell生命周期改为1,表示1秒,默认是0.所以你如果不改的话就看不到效果了.还有生出速率,默认也是0.这里也要改成正数,否则也是没效果的.

4.将度转换为弧度,将发射器cell的位置设置为视图的中点。
5.全部设置好,加到该加的地方
6.这个方法是iOS8的新方法,提供了一个方法来处理当前的特征集合,比如旋转设备。不熟悉特征集合吗?查阅iOS 8 by Tutorials.

运行:
[图片上传失败...(image-fd09c0-1587374045881)]

Layer Player:
[图片上传失败...(image-b56960-1587374045881)]


干货上完啦

[图片上传失败...(image-6d1a9e-1587374045881)]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容