如何在UIViewController中实现SKView的update(_ currentTime: CFTimeInterval)等效方法
UIViewController 实现 SKView 风格update回调的最优方案
UIKit 没有内置和 SKView.update(_ currentTime: CFTimeInterval) 对等的原生回调方法,要实现和屏幕刷新率同步的每帧更新逻辑,最优方案是使用 Core Animation 提供的 CADisplayLink,完全可以满足游戏元素持续移动的需求。
核心逻辑说明
SKView 的 update 回调本质是和屏幕渲染周期绑定的每帧触发机制,CADisplayLink 和它的运行逻辑完全一致:每次屏幕刷新前触发回调,默认和设备的屏幕刷新率(60Hz/120Hz ProMotion)完全对齐,精度远高于 NSTimer,不会出现动画卡顿、和渲染不同步的问题。
完整实现步骤
- 声明必要属性:存储
CADisplayLink实例和上一帧的时间戳,用来计算帧间隔保证不同设备移动速度一致
class GameViewController: UIViewController { // 你需要移动的目标元素示例 private let gameElement = UIView(frame: CGRect(x: 0, y: 100, width: 50, height: 50)) private var displayLink: CADisplayLink? private var lastFrameTimestamp: CFTimeInterval = 0 }
- 启动更新循环:在页面可见时初始化
CADisplayLink并加入主运行循环
override func viewDidLoad() { super.viewDidLoad() view.addSubview(gameElement) gameElement.backgroundColor = .red } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // 初始化displayLink,绑定update回调 displayLink = CADisplayLink(target: self, selector: #selector(handleFrameUpdate(_:))) // 添加到common模式,避免滚动UIScrollView时更新被暂停 displayLink?.add(to: .main, forMode: .common) // 初始化上一帧时间 lastFrameTimestamp = CACurrentMediaTime() }
- 实现update回调,编写元素移动逻辑
@objc private func handleFrameUpdate(_ sender: CADisplayLink) { let currentTime = sender.timestamp // 计算两帧之间的时间差 let deltaTime = currentTime - lastFrameTimestamp // 更新上一帧时间 lastFrameTimestamp = currentTime // 这里写你的游戏逻辑,和SKView的update用法完全一致 // 示例:让元素每秒横向移动150pt gameElement.frame.origin.x += 150 * deltaTime // 超出屏幕后重置位置 if gameElement.frame.minX > view.bounds.width { gameElement.frame.origin.x = -gameElement.frame.width } }
- 页面不可见时销毁
CADisplayLink,避免内存泄漏和不必要的性能消耗
override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) displayLink?.invalidate() displayLink = nil }
注意事项
- 不要使用
NSTimer实现该逻辑:NSTimer的触发精度受主线程任务调度影响很大,容易出现卡顿、跳帧,完全不适合游戏类的高实时性场景 - 自适应高刷新率:
CADisplayLink会自动跟随设备的ProMotion刷新率触发,你只需要通过deltaTime计算位移,就能保证所有设备上元素移动速度一致 - 自定义帧率需求:如果需要锁定30/60帧,iOS 15+可以通过
displayLink?.preferredFrameRateRange = .init(minimum: 30, maximum: 30)设置,旧iOS版本使用displayLink?.preferredFramesPerSecond = 30即可
内容的提问来源于stack exchange,提问作者SanMu
相关产品推荐
相关产品推荐

