CADisplayLink flashing effect

Hi there,


I am using the CADisplayLink timer to flash the screen of an iPhone at a frequency of 30 Hz. Since the refresh rate of the device is 60 Hz, I just need to change the background color in each frame in order to end up with a white frame, then a black frame, then a white frame, then a black frame, etc.

However, if I start the app and leave it running for a couple of minutes and then stop it and go back to the home screen, there is a faint flashing effect that is most visible near the borders of the screen. I haven't been able to find a solution to this, as the effect continues even after restarting the iPhone. What's more, the flashing effect fades away with time and eventually disappears.


I suspect this bug is beyond my code, but I would be grateful if you could prove me wrong.

Here is the buttonClicked function in my ViewController.m file (I'm using a Start/Stop button to start and stop flashing), where I initialise and invalidate the CADisplayLink:


- (IBAction)buttonClicked:(id)sender {

if ([self.startButton.currentTitle isEqual:@"START"]) {

self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];

[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

[sender setTitle:@"STOP" forState:UIControlStateNormal];

}

else if ([self.startButton.currentTitle isEqual:@"STOP"]) {

[self.displayLink invalidate];

self.displayLink = nil;

self.view.backgroundColor = [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:1];

[sender setTitle:@"START" forState:UIControlStateNormal];

}

}


And this is the tick function that gets called each frame:


- (void)tick:(CADisplayLink *) displayLink {

if ([self.view.backgroundColor isEqual:[UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:1]]) {

self.view.backgroundColor = [UIColor colorWithRed:255.0f/255.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:1];

}

else {

self.view.backgroundColor = [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:1];

}

}

Replies

Forgot to add, I'm using Xcode version 8.3.3 to build an app on an iPhone SE with iOS 10.3.2 on it.

In general, changing the appearance of UI elements (such as setting the background color) should be done on the main thread, and the "tick" method is unlikely to be called on the main thread. (The documentation isn't clear on this point, but it's likely on a background thread.) This could be enough of a problem to prevent the color change from being properly synchronized with the screen refresh, which in turn could give the appearance of different parts of the screen being updated at different times, which might be the "faint flashing" you're seeing.


So, try using "dispatch_async" to dispatch the color change back to the main queue, and thus the main thread.


Also, I suggest you use a simple BOOL flag to control which direction you change the color each time. Constructing the UIColor object might be more complex than it appears (since color management may be involved), and it's cheaper to construct the two colors once before the display link starts to run.


Something else to think about is that changing the background color causes the view's backing store to be re-written, which involves some (small) amount of GPU processing to change the pixel values. You could try using two different views, with different background colors preset, which means twoo backing stores. Then alternately hide and show the views. That way, there's no pixel coloring to be done, just composition of the view backing stores, which has to be done every frame anyway. (You should do some performance testing to find the optimal approach.)


The other thing to be careful of is that you have no guarantee that you're running at 30 Hz. You really should choose the current color based on the absolute time at which "tick" executes, in case the device is unable to keep up with the refresh rate, or refreshes faster (if it's more sophisticated hardware). CADisplayLink seems to have all the timing information you need, already built in.