My custom UI elements are not being updated while UIScrollView is scrolled - iphone

I have a spinning circle UI element that is updated by an NSTimer. I also have a UIScrollView that would 'block' the spinning circle while being scrolled. That was expected, because the timer and the scroll view where both in the same thread.
So I put the timer in a separate thread which basically works great! I can see it working because NSLogs keep coming even while scrolling.
But my problem is that my spinning circle is still stopping on scrolling! I suspect redrawing is halted until the next iteration of the main thread's run loop (?). So even if its angle is changed all the time, it might not be redrawn...
Any ideas what I can do? Thanks!

While scrolling, the main thread's run loop is in UITrackingRunLoopMode. So what you need to do is schedule your timer in that mode (possibly in addition to the default mode). I believe NSRunLoopCommonModes includes both the default and event tracking modes, so you can just do this:
NSTimer *timer = [NSTimer timerWithTimeInterval:0.42 target:foo selector:#selector(doSomething) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
By the way, you should do this all on the main thread. No need to spawn a background thread (unless each timer invocation is going to do some lengthy processing).

UIScrollView doesn't just block NSTimers, it blocks the entire main thread. And UIKit objects should be accessed on the main thread only (and, often, are limited in unpredictable ways if you try to dodge round that restriction), which is probably why — even if you have a clock coming in from an external thread — you're unable to post updates.
It's a bit of a dodge, but is there any way your animation can be achieved with CoreAnimation? Things tied to that continue working irrespective of what's happening in the main thread.

You are correct that UIScrollView does not update its content view during scrolling.
Probably your best bet -- and this is something of a hack -- is to display your animating view separately (perhaps within a frame-view, to get the free clipping) and use the scroll-view delegate callbacks to find out the current position of the scroll and position your animation accordingly.
NOTE: this suggestion is a hack! It's not best practices and it's certainly counter to simple, maintainable code without side effects. However, it's the only way I can think to get the display you want.
Another tack: re-think the importance of displaying animated contents of a scrolling view while it is scrolling.

Related

Running an endlessly looping animation on iOS

I have a simple UIView that draws itself using drawRect:. In order for the view to animate, the drawRect method needs to be called every say 0.05 seconds, so I use a repeating timer:
timer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self
selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
I don't know too much about run loops, and threads, and all that system stuff, so I want to know if this is the correct way to run an animation? This timer repeats itself endlessly. Is this something I should be worried about? Will this block the main thread? What can I do to minimize overall impact on performance?
The approach is not bad, but there are other ways to do it.
The timer's target method must take the timer as an argument, so instead of setting it for setNeedsDisplay directly, you should set up a method like this:
- (void)animationTimerDidFire:(NSTimer *)timer
{
[myView setNeedsDisplay];
}
If your view will always be visible, then you can just set-and-forget the timer. On the other hand, if it may go away because you switch to a different view, you will need to invalidate and recreate the timer as needed.
The main thread of your app uses a run loop and calls out to various methods in response to events, like user taps, system notifications (e.g., memory warning), and I/O arriving. If anything the run loop calls takes a long time to return, it will hold up everything in the queue. When you set up a timer, it is added to a list and that the run loop checks it each time through; if one of the timers is ready, it calls your method.
The end result is that timers are not exact: they might not fire as often as you like, might be called late, etc. Again, if your app is pretty simple, the main run loop won't be very busy and so a timer will probably be good enough. Just make sure your animation is based on actual time elapsed between calls, rather than assuming each call happens exactly 0.05 seconds apart.
Alternatives
If your animation simply involves flipping through some static images, UIImageView has some support for this.
If creating each frame of animation takes a noticeable amount of time (and you don't want to block the main thread), you could use a background queue to draw into an image (see CGBitmapContextCreate and CGBitmapContextCreateImage), then signal the main thread when a new image is ready to display. Anything that touches a view MUST happen on the main thread, but you can do the drawing on the background.
You also might want to read up on CALayer in the QuartzCore framework. This is what the UIView objects actually manipulate to draw on the screen. You may find that, instead of drawing, you can get the effects you want by manipulating some CALayer objects and letting Core Animation do the heavy lifting (e.g., you change the color from red to blue, Core Animation takes care of fading from one to the other).
Well, if you are using an overridden or custom method, you should use recursion and a completion block for calling the animation. I find it works much better than a timer, since timers aren't always exact and can cause some animation issues if you have the animations timed for cycling.
EDIT: I don't know much about using drawRect: and calling [self setNeedsDisplay] to update it, so I can't help you in that regard. Sorry.

How to move several buttons throughout the screen

I am creating an application where I have several buttons on the screen and I want to make them move throughout the screen. User can tap on that buttons and the buttons on which user taps gets removed from the screen.Hence I have to stop the timer which moves that button.
But my main concern is How to make all the bubbles move i.e. How to manage each of them?
I have made the use of NSTimer.
NSTimer* timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(moveTheButton) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
But this can be helpful in case if I have only one button. If there are more buttons its quite hard to manage the timers.
Also the number of buttons are dynamic.
Please suggest some alternate.
I have gone through few threads but didnt got the expected answer.
Thanks in advance for help.....
I would suggest using CAAnimations to move your buttons. This would allow you to animate your buttons smoothly without the use of NSTimers and would also be ideal for creating your button animations dynamically.
CAAnimations are created then added to a views layer. You can animate the rotation, position and size of a view using CAAnimations. It is also easy to add and remove animations from a views layer, making it ideal for dynamic content without the headache of multiple NSTimers.
How should a UIButton move? Do you mean something like randomly moving each button like in a Brownian motion? : D
Well, it really does not matter I guess, but let's consider that you have a generalized function that determines the behavior of each button given its current position, in this case you can use an NSArray to keep track of all your buttons positions at each time and just call this function over the single NSArray (then cycling through its elements) as the selector of the NSTimer.
If you do not have a single generalized function, but each button has its own, you could subclass UIButton for each kind of motion you have to take care and implement a method for each class that implements the correct motion. Then you can have a function called in NSTimer that cycles through an array of references of the buttons and calls the implemented motion method for each instance (via polymorphism)

performSelector unable to run while UITableView is dragged?

I have a UITableViewController with its default UITableView. I begin slowly dragging the table with my finger to scroll, i.e. not flicking it with my finger. Every time the table moves on-screen the scrollViewDidScroll method of the controller is called; when some conditions I've specified are met, one of these calls to scrollViewDidScroll uses performSelector:withObject:afterDelay to schedule some action at a later time.
However, I'm finding that the action will not execute until I release my finger. For example, if I set the afterDelay parameter to 2 seconds, but hold my finger for 5 seconds, when I release my finger and the action executes it's 3 seconds too late. Is there any way to allow the action (which is to update the UI and so must run in the main thread) to execute while the finger is still against the screen?
Thanks!
This is because when a UIScrollView (UITableView's superclass) is scrolling, it changes its runloop in order to prioritize the scrollView over whatever the application was doing. This is happening to make sure scrolling is as smooth as it can be.
try using this version of delayed method:
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
for the modes, I'd suggest starting with an array that contains the following:
[[NSRunloop currentRunLoop] currentMode],
NSDefaultRunLoopMode

UIView drawing problem

I have this big problem that i dont know how to fix. I have a UIView that i want to draw a scrolling background on. I am using NSTimer to update 30 frames per second but it seems to redraw one frame every 8 seconds. I am calling [self setNeedsDisplay] but it has no effect. I cant figure out why this is happening, does anyone have any tips?
Thanks for your time.
I have an NSTimer in my applicationDidFinishLaunching method
timer = [NSTimer scheduledTimerWithTimeInterval:0.0
target:self
selector:#selector(gameLoop:)
userInfo:nil
repeats:YES];
It's messaging this method:
-(void) gameLoop: (id) sender
{
[myView updateAll];
[myView setNeedsDisplay];
}
myView is a UIView. UpdateAll updates my code for drawing. It works fine, the problem seems to be the drawRect method is being executed so infrequently. I need to know how to make it execute 30 frames per second. I keep seeing people say that all you have to do is what i have above but setNeedsDisplay still only gets called every 8 seconds.
Thank you for your help...
Redrawing the view is an expensive operation, which will decrease performance considerably. If you can instead, just have the background as an image view move across the background using [UIView beginAnimation... or using CAAnimations.
Don't use NSTimer.
From the NS Timer reference
Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds.
I'm not an expert on the iPhone, but you probably need to look into animation on the platform to achieve the update interval you want.
It turns out that my code was just incredibly inefficient and that was causing the slow down not the timer. If you have a similar problem, make sure to test your drawRect with some simple drawing (such as drawing your Frames Per Second to the screen) before you assume it is not being executed. Thanks for viewing.

Timer and animation events trumping TouchesEnded events

I've implemented a tap-and-hold handler using an NSTimer that I first set in the TouchesBegan overload.
However, what I actually want is for an action to be continuously performed in quick-fire succession while the touch is being held. So, on timer expiry I call a handler to do the work, which then sets another timer and the cycle continues until the TouchesEnded comes in and cancels it, or another terminating condition is met.
This works fine, until my handler code triggers an animation to go off at the same time.
Now we have animation events and timer events going off, and in all that we need to handle TouchesEnded as well.
What I am finding is that, if the animation is triggered, and I set my timer to less than 0.025 seconds, my TouchesEnded event doesn't come through until the timer cycle stops (the other terminating condition). Setting a slower timer, or not triggering the animation, make it work (TouchedEnded comes in straight away), but are not what I want.
Obviously this is all on the device (release build - no NSLogs) - in the sim it all works fine
Is there any way of setting the relative priorty of these events - or is it likely I'm missing something else obvious here?
[Update]
I've worked around this in this instance by doing the continuous part without visual feedback until it's done (which from this users perspective is instant). I think this is ok for now. I'd still like to hear any more thoughts on this (Jeffrey's idea was good), but I'm not waiting on tenterhooks now.
Try writing your own Timer-type class by spawning off onto a thread. Example:
BOOL continue = YES; //outside of your #implementation
-(void)doLoop
{
while(continue){
[NSThread sleepForTimeInterval:.025];
[self performSelectorOnMainThread:#selector(whateverTheFunctionIs) waitUntilDone:YES];
}
}
and this would be started by [NSThread detatchNewThreadSelector:#selector(doLoop) toTarget:self withObject:nil]. This is not exactly threadsafe, but you can choose to wrap the boolean into a NSNumber and then do #synchronize on it if you so choose. Alternatively, after I wrote that little snippet I realized it would be better to do a check against the current NSTime instead of sleepForTimeInterval: but you get the point. :)