UIView drawing problem - iphone

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.

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.

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

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.

How to update a UILabel frequently?

I am currently working on a project where I request and parse multiple html sites in a controller. To give some feedback to the user I have created a second view, which gets displayed during processing of the data. It displays a status label and a progressbar. In my controller I have several points where I update my labels text. Unfortunately this works only sometimes. I guess thats because the label gets redrawn only once in a while and I try to do it probably more often than once a second.
It does not work to force a redraw with [label setNeedsDisplay];
I also made a thread which updates the labels text with the text of a global variable and also calls setNeedsDisplay on the label every second. But the results in the same. I only see certain changes, but not all.
Everything is setup properly and the label never is nil. When I log my updateMethod everything seems allright. It just does not get displayed!
Cheers
Here is the code of my threads
- (void)startUpdateStatusThread{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(updateFrequently) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)updateFrequently{
NSLog(#"updateFrequently: %#", currentStatus);
test++;
[self.statusLabel setText:[NSString stringWithFormat:#"%# - %i", currentStatus, test]];
[self.statusLabel setNeedsDisplay];
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:NO];
}
Am I right if I assume that you call your label's text-property as well as setNeedsDisplay from within your "Thread" that is parsing your websites?
Note that: changes to UIViews (or subclasses of UIView like your label) must be performed on the main thread.
What I recommend you to do is write a helper method that updates your label and calls setNeedDisplay, the from within your Parser-Thread call
[mainViewController performSelectorOnMainThread:#selector(yourUpdateLabelHelper:) withObject:nil waitUntilDone:NO];
that should get your job done.
You mention threads. Be aware that UIKit controls, such as UILabel, can only be updated from the main thread. Make sure you are only attempting to set the label's text from the main thread and then verify if you are still having issues.
EDIT due to question edit:
First, -setNeedsDisplay only tells the view it needs to redisplay the next time the screen is refreshed, it does not force a display at that time.
Second, the iPhone screen draws at about 60 hz. This means there is an update every 0.016666 or so seconds. You're trying to update much faster than that, so only about every 160th of your updates will actually be displayed.
Third, and probably most important, you don't seem to be making any threads, unless you call -startUpdateStatusThread from a background thread. Either way, once you hit -updateFrequently everything is back on the main thread. With the frequency you are scheduling that timer, you are probably overloading the CPU and making it so that the system does not have time to draw the label. So even though you are setting new text, you aren't giving it a chance to render. You need to reduce the frequency of that timer, and you probably need to rethink whatever it is you're trying to do.
I think that the creation of the timer should be in separate function:
Allow the repetition and also store the timer in a member variable you can close on dealloc.
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:YES];
}
The update function should operate as a callback function.
Regards
Assayag

How to fire 10 messages per second?

Problem: I need to fire values to an object 10 times per second. I must be able to start firing and stop firing.
I've discovered that the delegate of an UIScrollView gets notified in about the same time intervals, no matter how fast the scrolling is. You can easily see that if you NSLog the deltas from the offset changes. There must be a way to start firing a message 10 times per second until something says "stop". But how?
I would look into the NSTimer class. That should allow you to set up a timer with an arbitrary time span (and stop it when desired).
I'm not much of a Cocoa developer, but I'm pretty sure you want NSTimer.
Look into [NSTimer timerWithTimeInterval:invocation:repeats:], [NSTimer invalidate], and NSRunLoop.

How do I prevent Core Animation blocking my main thread?

I'm aware of the fact that Core Animation dispatches its animations in a seperate thread, as stated in the documentation. Nevertheless, my animations seem to be blocking my main thread. All animations start and finnish. (With or without kCATransactionDisableActions set as true.) but become jumpy and the main runloop stalls.
What am I doing wrong?
Conceptual example:
[NSTimer scheduledTimerWithTimeInterval:0.0333 target:self selector:#selector(gameEngine) userInfo:nil repeats:YES];
- (void)gameEngine
{
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
myLayer.position = CGPointMake( newX, newY);
[CATransaction commit];
}
Creating 30 new animations a second is a really bad idea, and not how Core Animation was intended to be used. Core Animation was designed around the idea that you tell the system where you want your layer (or layer-backed view) to end up, and it figures out the rest, including how many frames to drop to run the animation in the time frame you provide. The animations themselves will run on a background thread, but I believe there's a little setup that needs to be performed first which takes place on the calling thread (usually the main thread). This setup might take longer than 1/30th of a second, which would overload your main thread.
You goal should be to minimize the amount of interaction with Core Animation. If you have a complex, but scripted, motion path (or other property change), set it all up ahead of time using a CAKeyframeAnimation and just call that animation once. If there's something about the motion of your object that needs to be altered in response to user input, only do that when you get the actual input. Changing a property of a CALayer mid-animation will cause it to smoothly move from the middle of its current motion path to its new destination.
If you work with Core Animation in this fashion, you can simultaneously animate up to 50 moving translucent layers at 60 FPS on the iPhone.
Given that you are starting an animation 30 times a second I would imagine you are overwhelming the system. Do the previous animations even have time to start before you issue a new one?