3

I was using NSTimer for my iOS application but I wasn't getting the result I wanted because of SetNeedsDisplay.

I made some research and found the CADisplayLink which is giving me the result I want about the animation. Sadly I can't manage to put a timer like I do with NSTimer.

[NSTimer scheduledTimerWithTimeInterval:0.250 target:self selector:@selector(setGaugeLevel) userInfo:nil repeats:YES];

My CADisplayLink is calling my function too fast, I'd like to give it the same timer I had when I was using my NSTimer (0.250 sec).

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(setGaugeLevel)];
    [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

The setGaugeLevel function just have a loop inside with a SetValue function which contains, in every iteration:

[self setNeedsDisplay];

Is there any way to slow it down to get the result I want ?

Bryan ORTIZ
  • 306
  • 3
  • 17

2 Answers2

4

As @carton notes, CADisplayLink is for synchronizing yourself with the display. It is not a generic timer. It's specifically for cases like real-time video where you want to update in a way that is tied to the sync refresh. That clearly is not your problem here.

Your method as you describe it doesn't make a lot of sense. Calling setNeedsDisplay doesn't cause a view to immediately redraw. It just marks it to be redrawn at the next drawing cycle. It never makes sense to call setNeedsDisplay in a loop. setGaugeLevel has to return or there the view will never be updated (because you'll hang the UI queue).

If you just want to make a progress meter rise in .25s increments, then just increment the progress indicator's value and return. Call that with an NSTimer. When it gets to 100%, invalidate the timer so it stops.

You shouldn't even need to call setNeedsDisplay. Progress indicators know that they need to be redrawn when their value changes.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
3

CADisplayLink is linked to the display refresh rate, it (typically?) runs at 60Hz, i.e. 60 frames per second. You can't control the time interval at which it fires, but it has an option to set a frameInterval, allowing you to specify the "number of frames that must pass before the display link notifies the target again."

In your code, add a line to limit updates to roughly once every 0.25s:

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(setGaugeLevel)];
displayLink.frameInterval = 15 // 0.25s, assuming 60Hz display refresh rate
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

But if you need to accurate timing without locking to display refresh rate, then your best approach is probably still NSTimer. What problems are you running into?

carton
  • 1,020
  • 1
  • 8
  • 17
  • I guess an AppleTV must be able to display at 50Hz rather than 60 in order to play back PAL-region content; it'd be interesting to know whether HDMI devices are permitted to implement 50Hz display only and, if so, whether any PAL-region devices do and, assuming the AppleTV is compatible with those, therefore whether it will pretend it is running at 60Hz to third-party apps. And, under iOS as per the tag, I guess ditto when using an HDMI adaptor and video out. 50Hz over the physical link would definitely be the case under the old composite video out to a PAL-region screen. – Tommy Dec 30 '15 at 20:23
  • 3
    The purpose of `CADisplayLink` is to "allow your application to synchronize its drawing to the refresh rate of the display." As @Tommy indicated, it does not guarantee a time interval and may lead to unexpected results depending on software and hardware platform. Better to use `NSTimer` if you care about timing. – carton Dec 30 '15 at 20:31
  • This is now preferredFramesPerSecond in Swift 4.2 – DogCoffee Dec 13 '18 at 06:00