Consider this trivial sync animation using CADisplayLink
,
var link:CADisplayLink?
var startTime:Double = 0.0
let animTime:Double = 0.2
let animMaxVal:CGFloat = 0.4
private func yourAnim()
{
if ( link != nil )
{
link!.paused = true
//A:
link!.removeFromRunLoop(
NSRunLoop.mainRunLoop(), forMode:NSDefaultRunLoopMode)
link = nil
}
link = CADisplayLink(target: self, selector: #selector(doorStep) )
startTime = CACurrentMediaTime()
link!.addToRunLoop(
NSRunLoop.currentRunLoop(), forMode:NSDefaultRunLoopMode)
}
func doorStep()
{
let elapsed = CACurrentMediaTime() - startTime
var ping = elapsed
if (elapsed > (animTime / 2.0)) {ping = animTime - elapsed}
let frac = ping / (animTime / 2.0)
yourAnimFunction(CGFloat(frac) * animMaxVal)
if (elapsed > animTime)
{
//B:
link!.paused = true
link!.removeFromRunLoop(
NSRunLoop.mainRunLoop(), forMode:NSDefaultRunLoopMode)
link = nil
yourAnimFunction(0.0)
}
}
func killAnimation()
{
// for example if the cell disappears or is reused
//C:
????!!!!
}
There seems to be various problems.
At (A:), even though link
is not null, it may not be possible to remove it from a run loop. (For example, someone may have initialized it with link = link:CADisplayLink()
- try it for a crash.)
Secondly at (B:) it seems to be a mess ... surely there's a better (and more Swift) way, and what if it's nil even though the time just expired?
Finally in (C:) if you want to break the anim ... I got depressed and have no clue what is best.
And really the code at A: and B: should be the same call right, kind of a clean-up call.
The above is the best example for how to use CADisplayLink with efficiency. Thanks to @Fattie and @digitalHound
I could not resist adding my use of CADisplayLink and DisplayUpdater classes by 'digitalHound' in PdfViewer using WKWebView. My requirement was to continue auto scroll the pdf at the user selectable speed.
May be the answer here is not correct place, but I intent to show usage of CADisplayLink here. ( for others like me, who can implement their requirement. )
Example Calling from another view is as under.
To load the PDF file from Document folder.
This example may be 'modified' to load web page and auto-scroll them at user selected speed.
Regards
Sanjay.
Here’s a simple example showing how I’d go about implementing a
CADisplayLink
(in Swift 3):Points to note:
nil
here to represent the state in which the display link isn’t running – as there’s no easy way of getting this information from an invalidated display link.removeFromRunLoop()
, we’re usinginvalidate()
, which will not crash if the display link hasn’t already been added to a run-loop. However this situation should never arise in the first place – as we’re always immediately adding the display link to the run-loop after creating it.displayLink
private in order to prevent outside classes from putting it in an unexpected state (e.g invalidating it but not setting it tonil
).stopDisplayLink()
method that both invalidates the display link (if it is non-nil) and sets it tonil
– rather than copy and pasting this logic.paused
totrue
before invalidating the display link, as this is redundant.displayLink
after checking for non-nil, we’re using optional chaining e.gdisplayLink?.invalidate()
(which will callinvalidate()
if the display link isn’t nil). While force unwrapping may be ‘safe’ in your given situation (as you’re checking for nil) – it’s potentially unsafe when it comes to future refactoring, as you may re-structure your logic without considering what impact this has on the force unwraps.elapsed
time to the animation duration in order to ensure that the later animation logic doesn’t produce a value out of the expected range.displayLinkDidFire(_:)
takes a single argument of typeCADisplayLink
, as required by the documentation.I realize this question already has a good answer, but here's another slightly different approach that helps in implementing smooth animations independent of the display link frame rate.
**(Link to demo project available at the bottom of this answer - UPDATE: demo project source code now updated to Swift 4)
For my implementation I opted to wrap the display link in it's own class and setup a delegate reference that will get called with the delta time (the time between the last display link call and the current call) so we can perform our animations a little more smoothly.
I'm currently using this method to animate ~60 views around the screen simultaneously in a game.
First we're going to define the delegate protocol that our wrapper will call to notify of update events.
Next we're going to define our display link wrapper class. This class will take a delegate reference on initialization. When initialized it will automatically start our display link, and clean it up on deinit.
To use it you simply initialize an instance of the wrapper, passing in the delegate listener reference, then update your animations based on the delta time. In this example, the delegate passes the update call off to the animatable view (this way you could track multiple animating views and have each update their positions via this call).
Our moveable views update function looks like this:
If you'd like to look through a full demo project you can download it from GitHub here: CADisplayLink Demo Project