16

I have a view backed by a CAEAGLLayer, which is inside a UIScrollView. When I begin scrolling, the CADisplayLink that calls the -draw method of openGL view stops getting called.

I verified that my runloop start / stop methods don't get called when scrolling. The -draw method simply doesn't get called as soon as scrolling begins, and resumes getting called as soon as scrolling ends.

Does UIKit stop a CADisplayLink from firing as soon as scrolling starts?

The display link is added to the run loop like this:

[dl addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

Maybe there is a conflict with this run loop mode and UIScrollView? Are there other run loop modes or alternative solutions to keep a CADisplayLink firing even when a UIScrollView is scrolling?

I thought there can be more than just one CADisplayLink in any application. Is that wrong?

Proud Member
  • 40,078
  • 47
  • 146
  • 231
  • It would seem that this is the solution currently (2016) ... stackoverflow.com/a/4878182/294884 ... which I believe is what rickster explains at length. Note that QA on this issue is quite confusing, as Apple screwed everything up, and the behaviors have changed drastically over the years. – Fattie Dec 29 '16 at 13:56

1 Answers1

33

You're not in NSDefaultRunLoopMode while scrolling a UIScrollView; you're in UITrackingRunLoopMode. So any timer scheduled only for the former won't fire in the latter. You can add your CADisplayLink to multiple run loop modes by calling addToRunLoop:forMode: repeatedly, or call it once with NSRunLoopCommonModes, which covers both modes.

They talked about this in detail, and other issues with integrating scroll views with GL, at WWDC 2012 in Session 223: "Enhancing User Experience with Scroll Views"; I recommend watching the video, as there's lots of other stuff in there that's likely relevant to your situation.


An example in (2016) Swift3...

let d = CADisplayLink(target: self, selector: #selector(ThisClassName.updateAlpha))
d.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)


//and then, for example...
func updateAlpha() {
  let a = leader.layer.presentation()?.value(forKey: "opacity") as! CGFloat
  follower.alpha = a
  }
Fattie
  • 27,874
  • 70
  • 431
  • 719
rickster
  • 124,678
  • 26
  • 272
  • 326
  • So what can I do about it? How can I schedule a timer for multiple modes so it also fires while scrolling? Just two calls to -addToRunLoop:forMode: ? – Proud Member Sep 27 '12 at 17:10
  • Indeed @rickster regarding this issue, years later, it does seem to be pretty stable now. `.commonModes` finally seems to "just work". I edited in a Swift3 example, obviously feel free to delete/edit – Fattie Dec 29 '16 at 13:57
  • 1. Are you adding a new mode the current RunLoops mode? 2. If that's the main thread does that mean that you've added a new mode to the main thread's modes? Hence that mode won't be removed from it until the app is killed? Which basically means that the app will do a heaver kind of tracking events? I'm asking because I read From [here](https://developer.apple.com/documentation/corefoundation/1542137-cfrunloopaddcommonmode) "Once a mode is added to the set of common modes, it cannot be removed." meaning you can't remove that mode... – mfaani Jan 27 '20 at 21:39