CADisplayLink stops updating when UIScrollView scrolled - iphone

Title is quite self explanatory, but I have some animation being done in a loop triggered by CADisplayLink. However, as soon as I scroll a UIScrollView I have added to my view hierarchy, the animation stops immediately, only to return again when scrolling has completely stopped and come to a standstill....
Anyway to cancel this behaviour?

You can also mitigate the effects of this issue by using NSRunLoopCommonModes instead of NSDefaultRunLoopModes:
[displayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];

Run the display link (using -addToRunLoop:forMode:) on another thread with another run loop. So create a new thread, create a run loop on that thread, and run the CADisplayLink on that thread/run loop.

Use UITrackingRunLoopMode. It's specifically designed for scrolling stuffs.
Otherwise, just call render & present routine at -scrollViewDidScroll.
UIScrollView broken and halts scrolling with OpenGL rendering (related CADisplayLink, NSRunLoop)

Here you can find a better (and more complex) solution:
Animation in OpenGL ES view freezes when UIScrollView is dragged on iPhone
that allows you to use 'NSRunLoopCommonModes' and avoids OpenGL freezing when holding a finger without scrolling.
This is related to what Doug found (setting the frame interval of CADisplayLink to 2 instead of 1 fixing UIScrollView).

Actually CADisplayLink support multiple RunloopMode.
try this:
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:UITrackingRunLoopMode];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

NSRunLoopCommonModes seems to mess with the bounciness and continuous nature of the uiscrollview.

I found that if I set the frame interval to 2 instead of 1 (so 30 frames a second) everything works fine. So what I'm doing is setting it to 2 when my popover comes up and resetting it to 1 when it dismisses.

We fixed this same issue by changing the frameInterval from 1, to 2. This essentially halves the rendering rate of your OpenGL scene, but it still may render sufficiently for your needs.
frameInterval
The number of frames that must pass before the display
link notifies the target again.
#property(nonatomic) NSInteger frameInterval Discussion The default
value is 1, which results in your application being notified at the
refresh rate of the display. If the value is set to a value larger
than 1, the display link notifies your application at a fraction of
the native refresh rate. For example, setting the interval to 2 causes
the display link to fire every other frame, providing half the frame
rate.
Setting this value to less than 1 results in undefined behavior and is
a programmer error.

Related

Is CCScene replace during CCTransition possible?

Calling replaceScene: with a CCScene on CCDirector which is during a CCTransition results in dealloc not being called on any scene used, moreover, after this operation any scene is not being displayed.
Here is a link to the sample project
Shortest way to obtain this behavior is such method (I mean reproducing the problem):
SceneOne *destScene = [SceneOne node];
CCTransitionFade *transition = [[CCTransitionFade alloc]initWithDuration:2 scene:destScene];
[[CCDirector sharedDirector]replaceScene:transition];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[[CCDirector sharedDirector]replaceScene:[SceneTwo node]];
});
My question is: is there any feasible way to replace scenes when the CCTransition is being performed by the CCDirector?
I have implemented a delegate callback from CCDirector informing me about ending scene replacement, but this is never called if I push the iPhone home button during CCTransition.
If the answer is no, is there a cocos2d-iphone way to achieve goal described below?
Originally, this problem arised when I wanted to add "loading scene" when applicationDidEnterBackground (as a background task) or applicationWillEnterForeground, but I realized it has nothing to do with background execution. My ultimate goal is to provide seamless user experience while a game is waiting for Game Center authentication handler being called, and a "loading scene" being displayed from the very start of the app awaked from background would be sufficient (not only preventing user interaction which can be done many ways, but not showing previous game UI to the user). However, this solution is susceptible to the problem described above - if user taps the home button during scene transition he is going to get a very strange screen after bringing the game from background.
EDIT: After some more research I didn't find any satisfactory solution to replacing scene during CCTransition, however, problem described above was solved by not calling replaceScene but by adding a "loading" CCLayer to the visible CCScene, as #LearnCocos2D (thanks Steffen) suggested. This is not perfect, since adding a child during scene transition still has some narrow window (in the sense of running time) when strange results occur, but it is much better than replacing the scene. I would like to mention that it only concerns me when testing my game on 3GS, since newer devices are significantly faster and it is very difficult to reproduce the problem of "home button" clicking during CCTransition on something faster than 3GS.

Updating UI concurrently on iOS

I have two scroll views in my app, one containing UIImageViews, one containing UIButtons. Using NSTimer, I'm making them scroll automatically. However, if one of the scroll views is tampered with (a finger touches it and starts scrolling manually), the other scroll view stops as well. Is there any way to stop this from happening? Or is it normal?
Also, the UIButtons inside the second scroll view are tap-able, but they don't show the standard highlighting. If I enable the glow effect, it works, but not the standard highlighting. Is there anyway I can make this work as well?
My code for NSTimer is
[NSTimer scheduledTimerWithTimeInterval:0.018
target:self
selector:#selector(onTimerScrollText)
userInfo:nil
repeats:YES];
- (void)onTimerScrollText {
CGFloat x = self.textScroller.contentOffset.x;
x += 0.5;
[self.textScroller setContentOffset:CGPointMake(x, 0)];
}
And it's pretty much the same for the image scroller.
Thanks!
The initial problem of one scrollview no longer scrolling until the other manual scroll has finished has been solved.
The solution is simple. Each timer needs to be added to the run loop:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Now they both work regardless of whether one is interrupted by one's finger.

GLKViewController: incorrect fps

Hope there are some GLKViewController experts out there because I have some problems :)
Just a quick description of my app. I have a UINavigationController in which I push different screens.
At some point, I get to my game screen which is a subclass of UINavigationController. In this screen, in viewDidLoad I manually create a EAGLContext, GLKView and instantiate a new GLKViewController (to handle my update&draw calls).
I am setting a preferred fps of 30.
The problem is that the first 3-4 update calls come with the correct DT, but then I have 2-3 frames with 1 second between them. I measure the DT using controller.timeSinceLastUpdate.
So I get like:
dt=0.33
dt=0.33
dt=0.33
dt=1.07
dt=1.05
dt=0.33
dt=0.33
After this, I get valid only DT times. I have no idea why those frames have that kind of delay. I measured the time it takes me in the update & draw method, and it's nowhere near 1 second.
Also, I'm not loading any textures/creating any geometry. Everything is done at loading since it is a rather small game.
Also, if I pop the game screen controller and then push back another instance of the game screen, this new GLKViewController will only call my update method aproximately every 1 second.
Did anybody have a problem with the framerate when using GLKViewController?
Thanks,
The problem is that you don't know what else the device is doing between your refreshes :)
You might only spent 0.1s working on the next frame but if there is a memory warning then other bits of your app will also take time to process. I guess that the gl controller will do it's best to keep to the preferred frame rate but if lots is going on in the background then there's not much it can do about it.
As long as you make sure that your code is rendering as fast as possible and isn't spiking in the same way as the framerate then it's not your render path. From your question it sounds like you've already tested that.
The other thing you might want to do is to watch out for other notifications that might be passed into your app (i.e. memory warnings).
Finally, is there a pattern to the slow frames - do they coincide with a new image being loaded or a file access? Have you done as much as possible beforehand? EDIT - rereading your question makes me think that you've already done this, sorry!
Sorry I can't be any more use :(
Ok, so I finally figured it out. It turns out that it's not even related to the GLKViewController (surprise surprise!).
It had something to do with the way I'm displaying the game screen view controller, like this:
GameAreaViewController* gameController = [[GameAreaViewController alloc] init];
[UIView beginAnimations:#"animation" context:nil];
[self.navigationController pushViewController: gameController animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView setAnimationDuration:0.7f];
[UIView commitAnimations];
SAFE_DEL(gameController);
If I use an animation duration of 0.3f, then I don't get any lag. At 0.5f sometimes I get it and at 0.7 I was always getting it.

CADisplayLink OpenGL rendering breaks UIScrollView behaviour

There are a few similar questions out there on SO (links at end), but none of them has allowed me to fix my problem, so here goes:
I'm using OpenGL rendering to make an image tiling and caching library for use in a game project, and I want to hijack the physics of the UIScrollView to allow the user to navigate around the images (since it has nice bounce behaviour, might as well use it). So I have a UIScrollView which I'm using to get the rendering view for my textures, but there's a problem - moving around on the scroll view prevents the CADisplayLink from firing until the user has finished scrolling (which looks horrible). One temporary fix has been to use NSRunLoopCommonModes instead of the default run mode, but unfortunately this breaks some aspects of scroll view behaviour on certain phones I'm testing on (the 3GS and simulator seem to work fine, while the iPhone4 and the 3G don't).
Does anyone know how I could get around this clash between the CADisplayLink and the UIScrollView, or know how to fix the UIScrollView working in other run modes? Thanks in advance :)
Promised links to similar questions:
UIScrollView broken and halts scrolling with OpenGL rendering (related CADisplayLink, NSRunLoop)
Animation in OpenGL ES view freezes when UIScrollView is dragged on iPhone
It's possible that slow updates on the main thread triggered by the CADisplayLink are what's breaking UIScrollView's scrolling behavior here. Your OpenGL ES rendering might be taking long enough for each frame to throw off the timing of a UIScrollView when using NSRunLoopCommonModes for the CADisplayLink.
One way around this is to perform your OpenGL ES rendering actions on a background thread by using a Grand Central Dispatch serial queue. I did this in my recent update to Molecules (source code for which can be found at that link), and in testing with using NSRunLoopCommonModes on my CADisplayLink, I don't see any interruption of the native scrolling behavior of a table view that's onscreen at the same time as the rendering.
For this, you can create a GCD serial dispatch queue and use it for all of your rendering updates to a particular OpenGL ES context to avoid two actions writing to the context at the same time. Then, within your CADisplayLink callback you can use code like the following:
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
{
return;
}
dispatch_async(openGLESContextQueue, ^{
[EAGLContext setCurrentContext:context];
// Render here
dispatch_semaphore_signal(frameRenderingSemaphore);
});
where frameRenderingSemaphore is created earlier as follows:
frameRenderingSemaphore = dispatch_semaphore_create(1);
This code will only add a new frame rendering action onto the queue if one isn't in the middle of executing. That way, the CADisplayLink can fire continuously, but it won't overload the queue with pending rendering actions if a frame takes longer than 1/60th of a second to process.
Again, I tried this on my iPad here and found no disruption to the scrolling action of a table view, just a little slowdown as the OpenGL ES rendering consumed GPU cycles.
My simple solution is to halve the rendering rate when the run loop is in tracking mode. All my UIScrollViews now work smoothly.
Here is the code fragment:
- (void) drawView: (CADisplayLink*) displayLink
{
if (displayLink != nil)
{
self.tickCounter++;
if(( [[ NSRunLoop currentRunLoop ] currentMode ] == UITrackingRunLoopMode ) && ( self.tickCounter & 1 ))
{
return;
}
/*** Rendering code goes here ***/
}
}
The answer at the following post works very well for me (it appears to be quite similar to Till's answer):
UIScrollView pauses NSTimer until scrolling finishes
To summarize: disable the CADisplayLink or GLKViewController render loop when the UIScrollView appears and start a NSTimer to perform the update/render loop at the desired framerate. When the UIScrollView is dismissed/removed from the view hierarchy, re-enable the displayLink/GLKViewController loop.
In the GLKViewController subclass I use the following code
on appear of UIScrollView:
// disable GLKViewController update/render loop, it will be interrupted
// by the UIScrollView of the MPMediaPicker
self.paused = YES;
updateAndRenderTimer = [NSTimer timerWithTimeInterval:1.0f/60.0f target:self selector:#selector(updateAndRender) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:updateAndRenderTimer forMode:NSRunLoopCommonModes];
on dismiss of UIScrollView:
// enable the GLKViewController update/render loop and cancel our own.
// UIScrollView wont interrupt us anymore
self.paused = NO;
[updateAndRenderTimer invalidate];
updateAndRenderTimer = nil;
Simple and effective. I'm not sure if this could cause artifacts/tearing of some sort since the rendering is decoupled from screen refreshes, but using CADisplayLink with NSRunLoopCommonModes totally breaks the UIScrollView in our case. Using NSTimer looks just fine for our app and definitely a whole lot better than no rendering.
Even though this is not the perfect solution, it still might work as a workaround;
You could ignore the display link availability and use NSTimer for updating your GL-layer instead.

Animation in OpenGL ES view freezes when UIScrollView is dragged on iPhone

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?
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
}