CADisplayLink works with lower frame rate on iOS5.1

I use CADisplayLink in my application for iPhone.

Here is the relevant code:

 SMPTELink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onTick)]; SMPTELink.frameInterval = 2;//30fps 60/n = fps [SMPTELink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 

onTick is thus called each frame 30FPS (1/30 second). This works GREAT on iOS6 + - does exactly what I need. However, when I launched my application on iPhone 4 running iOS5.1, the onTick method worked a little slower than with iOS6. Almost as if he was launching his 29FPS . After a bit, it was not synced with iOS6 iPhone 5.

The code in the onTick method does not take much time (it was one of my thoughts ...), and it is not an iPhone, because the application works fine on iPhone 4 with iOS6.

Does CADisplayLink function differently in iOS5.1 ? Any possible workarounds / solutions?

+6
source share
3 answers

I canโ€™t speak with the differences of iOS 5.xv 6.x, but when I use CADisplayLink , I never record things like โ€œmove x pixels / pointsโ€ at each iteration, but I look at the timestamp (more precisely, the delta between my initial timestamp and current timestamp ) and calculate the location depending on how much time has passed, and not on how many frames have passed. Thus, the frame rate does not affect the speed of movement, but rather on smoothness. (And the difference between 30 and 29 is likely to be indistinguishable.)

To quote from the Link to the CADisplayLink class :

After the link to the image is associated with the launch cycle, the selector on the target call is called when the contents of the screens need to be updated. The target can read the timestamp reference values โ€‹โ€‹to get the display time of the previous frame. For example, an application that displays movies can use a timestamp to calculate the next frame of a video. An application that performs its own animations can use a timestamp to determine where and how displayed objects appear in the upcoming frame. The duration property provides the time between frames. You can use this value in your application to calculate the display frame rate, the approximate display time of the next frame, and adjust the drawing behavior so that the next frame is prepared in time for display.


As a random example, here I am animating a UIBezierPath using the number of seconds elapsed as a parameter.

Or, conversely, if you are dealing with a UIImage frame UIImage , you can calculate the frame number as follows:

 @property (nonatomic) CFTimeInterval firstTimestamp; - (void)handleDisplayLink:(CADisplayLink *)displayLink { if (!self.firstTimestamp) self.firstTimestamp = displayLink.timestamp; CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; // now do whatever you want with this frame number } 

Or, even better, to avoid the risk of losing a frame, go ahead and let it run at 60 frames per second and just determine if the frame needs to be updated, and thus you will reduce the risk of frame dropping.

 - (void)handleDisplayLink:(CADisplayLink *)displayLink { if (!self.firstTimestamp) self.firstTimestamp = displayLink.timestamp; CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames; if (frameNumber != self.lastFrame) { // do whatever you want with this frame number ... // now update the "lastFrame" number property self.lastFrame = frameNumber; } } 

But often the number of frames is not needed at all. For example, to move a UIView in a circle, you can do something like:

 - (void)handleDisplayLink:(CADisplayLink *)displayLink { if (!self.firstTimestamp) self.firstTimestamp = displayLink.timestamp; CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp); self.animatedView.center = [self centerAtElapsed:elapsed]; } - (CGPoint)centerAtElapsed:(CFTimeInterval)elapsed { CGFloat radius = self.view.bounds.size.width / 2.0; return CGPointMake(radius + sin(elapsed) * radius, radius + cos(elapsed) * radius); } 

By the way, if you use tools to measure the frame rate, this may seem slower than on the device itself. For a matte comment, for the exact frame rate, you should measure it programmatically on a real device using the release build.

+18
source

Rob's answer is absolutely right. You are not worried about the frame rate of CADisplayLink; in fact, you should not even expect the timer to fire with something like regularity. Your task is to divide the desired animation according to the desired timeline and draw a frame that you actually get every time the timer fires, summing up the accumulated time stamps.

Here is a sample code from my book:

 if (self->_timestamp < 0.01) { // pick up and store first timestamp self->_timestamp = sender.timestamp; self->_frame = 0.0; } else { // calculate frame self->_frame = sender.timestamp - self->_timestamp; } sender.paused = YES; // defend against frame loss [_tran setValue:@(self->_frame) forKey:@"inputTime"]; CGImageRef moi3 = [self->_con createCGImage:_tran.outputImage fromRect:_moiextent]; self->_iv.image = [UIImage imageWithCGImage:moi3]; CGImageRelease(moi3); if (self->_frame > 1.0) { [sender invalidate]; self->_frame = 0.0; self->_timestamp = 0.0; } sender.paused = NO; 

In this code, the _frame value starts between 0 (we are just starting the animation) and 1 (we finished the animation), and in the middle I just do what it takes for this particular situation to draw this frame. To make the animation longer or shorter, simply multiply the scale factor when setting up _frame ivar.

Also note that you should never test in Simulator, as the results are completely meaningless. Only the device starts CADisplayLink correctly.

(Example from here: http://www.apeth.com/iOSBook/ch17.html#_cifilter_transitions )

+3
source

Basic Swift version of other answers (no animation code)

 class BasicStopwatch { var timer: CADisplayLink! var firstTimestamp: CFTimeInterval! var elapsedTime: TimeInterval = 0 let formatter: DateFormatter = { let df = DateFormatter() df.dateFormat = "mm:ss.SS" return df }() func begin() { timer = CADisplayLink(target: self, selector: #selector(tick)) timer.preferredFramesPerSecond = 10 // adjust as needed timer.add(to: .main, forMode: .common) } @objc func tick() { if (self.firstTimestamp == nil) { print("Set first timestamp") self.firstTimestamp = timer!.timestamp return } elapsedTime = timer.timestamp - firstTimestamp /// Print raw elapsed time // print(elapsedTime) /// print elapsed time print(elapsedTimeAsString()) /// If need to track frames // let totalFrames: Double = 20 // let frameNumber = (elapsedTime * Double(timer!.preferredFramesPerSecond)).truncatingRemainder(dividingBy: totalFrames) // print("Frame ", frameNumber) } func elapsedTimeAsString() -> String { return formatter.string(from: Date(timeIntervalSinceReferenceDate: elapsedTime)) } } 

using

 let watch = BasicStopwatch() watch.begin() 
0
source

All Articles