24

I have an animated transparent OpenGL ES subview (a modification of Apple's template EAGLView class) which draws a rotating sphere. Just like Apple's example, CADisplayLink is used on devices where available.

On the same screen, there is a UIScrollView containing UIButtons that can be selected. When the user scrolls the UIScrollView, the animation of my EAGLView freezes. This behavior is reproduced on iOS Simulator 4.2 and on iPhone OS 3.1.3 on an iPhone 2G device.

Any ideas on what to do to prevent pause of the EAGLView, apart from coding my own scroll view?

Ivan Vučica
  • 9,529
  • 9
  • 60
  • 111
  • 2016 - It would seem that **.commonModes** is indeed the solution for typical modern iOS. See: http://stackoverflow.com/a/4878182/294884 It's quite incredible there has been no QA on this for five years. – Fattie Dec 29 '16 at 13:51

1 Answers1

59

Whether CADisplayLink fires during scrolls depends on the mode with which you add it to the run loop. Probably you have this, somewhere:

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

UIApplication adds a run loop mode, UITrackingRunLoopMode, for 'tracking in controls', which includes when a scrollview is scrolling. So at that point the runloop is switched out of the default mode and hence your display link (and also any timers, NSURLConnections, etc, added in the default mode) will fail to fire until default mode is restored.

Quick fix: change your code to:

[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

UITrackingRunLoopMode is considered one of the common modes.

Spending too much time interrupting UIKit can lead to some very poor control responsiveness, so you need to be careful. It'd be to diverge from the topic massively, but although OpenGL is modal and therefore not particularly threading friendly, you can use an EAGLSharegroup to do rendering on a separate thread and then push it onto the main thread.


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
Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Thanks! For this project, I don't really worry about blocking UIKit, since I'm just rendering a textured sphere in the GUI, so GL should leave plenty of time for UIKit processing :-) – Ivan Vučica Feb 02 '11 at 19:50
  • 3
    Just a small addition! While this works great, it nevertheless triggers a bug on iOS4 devices where UIScrollView never catches the touch-up event and freezes in the scroll mode. This does not appear in the iOS4 Simulator nor on my iPhone2G with iOS3.1.3. Solution turned out to be quite simple: I switched to NSTimer-based codepath even if displaylink is supported, and I scheduled the NSTimer in NSRunLoopCommonModes. – Ivan Vučica Feb 28 '11 at 12:10
  • 1
    See this question for more info on this solution: http://stackoverflow.com/questions/5944050/cadisplaylink-opengl-rendering-breaks-uiscrollview-behaviour – Ricardo Sanchez-Saez May 14 '11 at 17:01
  • Great! This helped a lot, and saved me tons of time! – msgambel Sep 22 '11 at 05:14
  • Hm... The view doesn't seem to be refreshed, even though rendering IS called while scrolling. What I did is I added the displayLink to NSRunLoopCommonModes and I'm even calling view.setNeedsDisplay() in the function linked to the DisplayLink. Do you have any ideas what else should I do? – Lukasz Czerwinski Oct 17 '15 at 03:11
  • Thanks for this @Tommy - indeed now that it is 2016. It does seem that `.commonModes` works fairly reliably, with a quick check on modern devices. It's really incredible there have been no other posts about this in half a decade! Thanks again! – Fattie Dec 29 '16 at 13:50