说到定时器我们常常会想到 Timer
,我之前也写过不少相关文章。其实除了 Timer
,还有 CADisplayLink
这个定时器。不同于 Timer
需要在初始化时设置一个定时周期,CADisplayLink
是一个同步屏幕刷新频率的定时器。
1、CADisplayLink 的介绍
(1)由于 iOS 设备的屏幕刷新频率是固定的 60Hz,因此 CADisplayLink
的 selector 默认调用周期也是是每秒 60 次(这个周期可以通过 frameInterval 属性设置)。
(2)不同于 Timer
因为阻塞问题会造成触发时间的延迟,CADisplayLink
在正常情况下会在每次刷新结束时被调用,因此精确度相当高。适合做 UI 的不停重绘,比如自定义动画引擎或者视频播放的渲染。
2、样例效果图
(1)下面使用 CADisplayLink
实现一个波浪效果。波浪会从右往左不断地移动,同时上方的文字也会随之上下浮动。
(2)波浪曲线其实是一条正弦曲线,整个动画的实现原理是在每次屏幕刷新时,改变曲线的偏移量再重新绘制,从而实现波浪起伏的效果。



3、样例代码
ViewController.swift(主视图控制器)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 创建文本标签
let label = UILabel()
label.text = "正在加载中......"
label.textColor = .white
label.textAlignment = .center
label.frame = CGRect(x: (screenWidth() * 0.5 - 100), y: 0, width: 200, height: 50)
// 创建波浪视图
let waveView = WaveView(frame: CGRect(x: 0, y: 0, width: screenWidth(),
height: 130))
// 波浪动画回调
waveView.closure = {centerY in
// 同步更新文本标签的y坐标
label.frame.origin.y = waveView.frame.height + centerY - 55
}
// 添加两个视图
self.view.addSubview(waveView)
self.view.addSubview(label)
// 开始播放波浪动画
waveView.startWave()
}
// 返回当前屏幕宽度
func screenWidth() -> CGFloat {
return UIScreen.main.bounds.size.width
}
}
WaveView.swift(自定义的波浪视图组件)
import UIKit
import QuartzCore
// 波浪曲线动画视图
// 波浪曲线公式:y = h * sin(a * x + b); h: 波浪高度, a: 波浪宽度系数, b: 波的移动
class WaveView: UIView {
// 波浪高度h
var waveHeight: CGFloat = 7
// 波浪宽度系数a
var waveRate: CGFloat = 0.01
// 波浪移动速度
var waveSpeed: CGFloat = 0.05
// 真实波浪颜色
var realWaveColor: UIColor = UIColor(55, 153, 249)
// 阴影波浪颜色
var maskWaveColor: UIColor = UIColor(55, 153, 249, 0.3)
// 波浪位置(默认是在下方)
var waveOnBottom = true
// 波浪移动回调
var closure: ((_ centerY: CGFloat)->())?
// 定时器
private var displayLink: CADisplayLink?
// 真实波浪
private var realWaveLayer: CAShapeLayer?
// 阴影波浪
private var maskWaveLayer: CAShapeLayer?
// 波浪的偏移量
private var offset: CGFloat = 0
// 视图初始化
override init(frame: CGRect) {
super.init(frame: frame)
initWaveParame()
}
// 视图初始化
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initWaveParame()
}
// 组件初始化
private func initWaveParame() {
// 真实波浪配置
realWaveLayer = CAShapeLayer()
var frame = bounds
realWaveLayer?.frame.origin.y = frame.size.height - waveHeight
frame.size.height = waveHeight
realWaveLayer?.frame = frame
// 阴影波浪配置
maskWaveLayer = CAShapeLayer()
maskWaveLayer?.frame.origin.y = frame.size.height - waveHeight
frame.size.height = waveHeight
maskWaveLayer?.frame = frame
layer.addSublayer(maskWaveLayer!)
layer.addSublayer(realWaveLayer!)
}
// 开始播放动画
func startWave() {
// 开启定时器
displayLink = CADisplayLink(target: self, selector: #selector(self.wave))
displayLink!.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
}
// 停止播放动画
func endWave() {
// 结束定时器
displayLink!.invalidate()
displayLink = nil
}
// 定时器响应(每一帧都会调用一次)
@objc func wave() {
// 波浪移动的关键:按照指定的速度偏移
offset += waveSpeed
// 起点y坐标(没有波浪的一侧)
let startY = self.waveOnBottom ? 0 : frame.size.height
let realPath = UIBezierPath()
realPath.move(to: CGPoint(x: 0, y: startY))
let maskPath = UIBezierPath()
maskPath.move(to: CGPoint(x: 0, y: startY))
var x = CGFloat(0)
var y = CGFloat(0)
while x <= bounds.size.width {
// 波浪曲线
y = waveHeight * sin(x * waveRate + offset)
// 如果是下波浪还要加上视图高度
let realY = y + (self.waveOnBottom ? frame.size.height : 0)
let maskY = -y + (self.waveOnBottom ? frame.size.height : 0)
realPath.addLine(to: CGPoint(x: x, y: realY))
maskPath.addLine(to: CGPoint(x: x, y: maskY))
// 增量越小,曲线越平滑
x += 0.1
}
let midX = bounds.size.width * 0.5
let midY = waveHeight * sin(midX * waveRate + offset)
if let closureBack = closure {
closureBack(midY)
}
// 回到起点对侧
realPath.addLine(to: CGPoint(x: frame.size.width, y: startY))
maskPath.addLine(to: CGPoint(x: frame.size.width, y: startY))
// 闭合曲线
maskPath.close()
// 把封闭图形的路径赋值给CAShapeLayer
maskWaveLayer?.path = maskPath.cgPath
maskWaveLayer?.fillColor = maskWaveColor.cgColor
realPath.close()
realWaveLayer?.path = realPath.cgPath
realWaveLayer?.fillColor = realWaveColor.cgColor
}
}
UIColor+.swift(UIColor 扩展)
import UIKit
//UIColor扩展
extension UIColor {
//使用rgb方式生成自定义颜色
convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat) {
let red = r / 255.0
let green = g / 255.0
let blue = b / 255.0
self.init(red: red, green: green, blue: blue, alpha: 1)
}
//使用rgba方式生成自定义颜色
convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat, _ a : CGFloat) {
let red = r / 255.0
let green = g / 255.0
let blue = b / 255.0
self.init(red: red, green: green, blue: blue, alpha: a)
}
}
附:上波浪样式
4、效果图
波浪视图组件WaveView
还提供了个 waveOnBottom
属性,我们将其设置为 false
即可让波浪显示在组件视图的上侧。

5、样例代码
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 创建文本标签
let label = UILabel()
label.text = "正在加载中......"
label.textColor = UIColor(55, 153, 249)
label.textAlignment = .center
label.frame = CGRect(x: (screenWidth() * 0.5 - 100), y: 0, width: 200, height: 50)
// 创建波浪视图
let waveView = WaveView(frame: CGRect(x: 0, y: screenHeight() - 60,
width: screenWidth(), height: 60))
// 波浪显示在上方
waveView.waveOnBottom = false
// 波浪动画回调
waveView.closure = {centerY in
// 同步更新文本标签的y坐标
label.frame.origin.y = waveView.frame.origin.y + centerY - 60
}
// 添加两个视图
self.view.addSubview(waveView)
self.view.addSubview(label)
// 开始播放波浪动画
waveView.startWave()
}
// 返回当前屏幕宽度
func screenWidth() -> CGFloat {
return UIScreen.main.bounds.size.width
}
// 返回当前屏幕高度
func screenHeight() -> CGFloat {
return UIScreen.main.bounds.size.height
}
}
网友评论