我意识到这个问题已经有了一个很好的答案,但这是另一种稍微不同的方法,它有助于实现独立于显示链接帧速率的平滑动画。
**(此答案底部提供演示项目的链接 - 更新:演示项目源代码现已更新为 Swift 4)
对于我的实现,我选择将显示链接包装在它自己的类中并设置一个委托引用,它将使用增量时间(最后一次显示链接调用和当前调用之间的时间)调用,这样我们就可以执行我们的动画多一点顺利。
我目前正在使用这种方法在游戏中同时为屏幕周围的大约 60 个视图设置动画。
首先,我们将定义我们的包装器将调用以通知更新事件的委托协议。
// defines an interface for receiving display update notifications
protocol DisplayUpdateReceiver: class {
func displayWillUpdate(deltaTime: CFTimeInterval)
}
接下来我们将定义我们的显示链接包装类。此类将在初始化时采用委托引用。初始化时,它会自动启动我们的显示链接,并在 deinit 上清理它。
import UIKit
class DisplayUpdateNotifier {
// **********************************************
// MARK: Variables
// **********************************************
/// A weak reference to the delegate/listener that will be notified/called on display updates
weak var listener: DisplayUpdateReceiver?
/// The display link that will be initiating our updates
internal var displayLink: CADisplayLink? = nil
/// Tracks the timestamp from the previous displayLink call
internal var lastTime: CFTimeInterval = 0.0
// **********************************************
// MARK: Setup & Tear Down
// **********************************************
deinit {
stopDisplayLink()
}
init(listener: DisplayUpdateReceiver) {
// setup our delegate listener reference
self.listener = listener
// setup & kick off the display link
startDisplayLink()
}
// **********************************************
// MARK: CADisplay Link
// **********************************************
/// Creates a new display link if one is not already running
private func startDisplayLink() {
guard displayLink == nil else {
return
}
displayLink = CADisplayLink(target: self, selector: #selector(linkUpdate))
displayLink?.add(to: .main, forMode: .commonModes)
lastTime = 0.0
}
/// Invalidates and destroys the current display link. Resets timestamp var to zero
private func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
lastTime = 0.0
}
/// Notifier function called by display link. Calculates the delta time and passes it in the delegate call.
@objc private func linkUpdate() {
// bail if our display link is no longer valid
guard let displayLink = displayLink else {
return
}
// get the current time
let currentTime = displayLink.timestamp
// calculate delta (
let delta: CFTimeInterval = currentTime - lastTime
// store as previous
lastTime = currentTime
// call delegate
listener?.displayWillUpdate(deltaTime: delta)
}
}
要使用它,您只需初始化包装器的一个实例,传入委托侦听器引用,然后根据增量时间更新您的动画。在此示例中,委托将更新调用传递给动画视图(这样您可以跟踪多个动画视图并通过此调用更新它们的位置)。
class ViewController: UIViewController, DisplayUpdateReceiver {
var displayLinker: DisplayUpdateNotifier?
var animView: MoveableView?
override func viewDidLoad() {
super.viewDidLoad()
// setup our animatable view and add as subview
animView = MoveableView.init(frame: CGRect.init(x: 150.0, y: 400.0, width: 20.0, height: 20.0))
animView?.configureMovement()
animView?.backgroundColor = .blue
view.addSubview(animView!)
// setup our display link notifier wrapper class
displayLinker = DisplayUpdateNotifier.init(listener: self)
}
// implement DisplayUpdateReceiver function to receive updates from display link wrapper class
func displayWillUpdate(deltaTime: CFTimeInterval) {
// pass the update call off to our animating view or views
_ = animView?.update(deltaTime: deltaTime)
// in this example, the animatable view will remove itself from its superview when its animation is complete and set a flag
// that it's ready to be used. We simply check if it's ready to be recycled, if so we reset its position and add it to
// our view again
if animView?.isReadyForReuse == true {
animView?.reset(center: CGPoint.init(x: CGFloat.random(low: 20.0, high: 300.0), y: CGFloat.random(low: 20.0, high: 700.0)))
view.addSubview(animView!)
}
}
}
我们的可移动视图更新函数如下所示:
func update(deltaTime: CFTimeInterval) -> Bool {
guard canAnimate == true, isReadyForReuse == false else {
return false
}
// by multiplying our x/y values by the delta time new values are generated that will generate a smooth animation independent of the framerate.
let smoothVel = CGPoint(x: CGFloat(Double(velocity.x)*deltaTime), y: CGFloat(Double(velocity.y)*deltaTime))
let smoothAccel = CGPoint(x: CGFloat(Double(acceleration.x)*deltaTime), y: CGFloat(Double(acceleration.y)*deltaTime))
// update velocity with smoothed acceleration
velocity.adding(point: smoothAccel)
// update center with smoothed velocity
center.adding(point: smoothVel)
currentTime += 0.01
if currentTime >= timeLimit {
canAnimate = false
endAnimation()
return false
}
return true
}
如果您想查看完整的演示项目,可以从 GitHub 下载:CADisplayLink 演示项目