UIView animation takes over CPU time - iphone

i wonder how i can have continuos and repeating animations on my Views w/o freezing the rest of my app - since my current approaches lead into 100% animation, 0% user interaction possible - which is kind of .. bad.
the animation in general is simple: i want to make an uilabel act like a siren, altering the alpha-property from 1 to 0 and back and so forth.
the basic animation block therefore is:
[UIView animateWithDuration:0.4f
delay:0.0f
options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionBeginFromCurrentState
animations:^(void){warningMessage.alpha = 0;}
completion:NULL];
i tried to call it in background, in backgroundthreads, even in an async GCD block, each time i loose control of the app - but the animation works fine.
(ok, apple docs say it MUST happen on main thread, but i tried..)
Now again, the question: how to have both of it? is it even possible in that way?
Thanks in advance!
P.S.: Sry if this is quite a repost, did not find a proper solution/question here!
P.P.S: yes, i know that sirens dont alter their alpha propery :P

did you try adding the UIViewAnimationOptionAllowUserInteraction to the options flag?

Well.. doing animations with uikit is for sure easy .. but I would not call it very efficient. You just animate the alpha, but there is much more happening in the background. :)
Animations already run in a different thread.. so starting it on an other thread won't help you.
As a solution I would register a CADisplayLink in your application and change the alpha of the view in it's update function ... that will be much more efficient if you run it all the time.

Related

How to save data with a delay?

I'm wondering if iOS allows one to do the following:
I have a puzzle game and I've been working on saving data when the user returns to the home screen. To do this, using NSNotificationCenter, I had one of my game classes to observe [UIApplication sharedApplication]'s ApplicationWillResignActive method so when that happens I save my game state. But if the user decides to exit while animations are going on, the game will save a midState where the model values are still changing and that will often cause crashes. My question is if it is possible to somehow delay the saving process (even though it is on the background) until the animations are complete (or some variable equals 1)?
My first idea is to create scheduled event with NSTimer to try to save until everything is set. Any ideas would be appreciated. Thank you
You can use performSelector:withObject:afterDelay:
// Call doSomething after a 1 second delay
[self performSelector:#selector(doSomething) withObject:nil afterDelay:1.0f];
Rather than trying to delay the saving, especially in ApplicationWillResignActive, you should look into how to stop the animation and record the expected final values of the animation. Depending on the method of animation (UIView static methods, block based, 3rd party) there is usually a way to stop them, and since you define what the animation does you should already know the final state.
In the case of block based animations:
How to cancel UIViews block-based animation?

UIViewAnimation blocks user interaction

I have a UIView following the users finger as they move it around inside my app. Sometimes, other things on screen are animating with UIViewAnimation blocks, but this freezes the tracking of their finger, so if they continue moving their finger during the animation, it won't follow. How can i stop the animation from blocking up the main thread?
Try using UIViewAnimationOptionAllowUserInteraction with [UIView animateWithDuration:delay:options:animations:completion:]
you can use the NSObject's method: performSelector:onThread:withObject:waitUntilDone:
More details in Apple NSObject Documentation
If there is any other thing on the screen is animated, that would also be done in main thread. And the current finger tracking would also be done in main thread. So definitely there would be some blocking.
To get rid of that, we can optimize our code using blocks and GCD.
Note that if you link for iOS 5 this problem will go away all by itself. Under iOS 5, block-based animation of a view does not turn off user interaction for other views. This is what Apple should have done in the first place.

Animation inside a UIScrollView

I want to fade-out a view as it is scrolling inside a parent UIScrollview. When the fade-out animation begins, the scroll view stops scrolling. It jumps to the correct position when the fade is complete.
My fade-out is achieved with animateWithDuration and block objects, triggered upon a page-change I detect in scrollViewWillBeginDragging.
Does anyone know how to make them both happen simultaneously? Just to be clear, I am not 'animating' the UIScrollView scrolling - rather it is happening via user interaction of swiping.
EDIT:
Here is the code I'm using to fade the UIView. This code is in a UIViewController derived class, which is the delegate for a UIScrollView. When the user starts dragging his finger, I want to fade out the subView. But when the user starts draggin a finger, the subview fades and the scrolling stops. After the subView has completely faded out, the the scroll view will then snap to the location where the user's finger is.
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView
{
[UIView animateWithDuration:0.5
animations:^
{
self.subView.alpha = 0.0f;
}
completion:^(BOOL finished) { }];
}
A little late, but if you want to keep using blocks, you can use:
animateWithDuration:delay:options:animation:complete:
add "UIViewAnimationOptionAllowUserInteraction" to options to allow interaction while scrolling.
I'm sure that you will still have the lag problem. Here's the best way I can explain it. Please forgive me in advance since I'm probably using the wrong terms. All animations must run on the main thread. When you call an animation, iOS first *P*rocesses then it *R*enders before it generates *F*rames. It looks like this.
PPPPRRRRFFFFFFFFFFFFFFFFFF
But since ScrollViews don't know how long your animation is going to be or when it will end, it has to perform the animation like this.
PRFPRFPRFPRFPRFPRFPRFPRF
My theory is that the lag you are experiencing has to do with these two calls colliding on the main thread at the same time. I'm not sure how you would solve this problem other than with a faster chip. I've that you could push one animation to the CPU and one to the GPU, but I'm not that advanced at programming yet.
very interesting ... I've checked this out, and yes, i have the same effect ... Well, it seems that the animateWithDuration somehow blocks the main thread ... which is not logical, and the documentation doesn't say anything about it either ..
However there is an easy workaround, something similar to this: (i've set the animation duration to 3 so i can see that it's working while i'm moving my scroll view :) ...)
[UIView beginAnimations:#"FadeAnimations" context:nil];
[UIView setAnimationDuration:3];
self.subview.alpha = 0.0f;
[UIView commitAnimations];
I would suggest, since the opacity is based on the user's finger's movements in the UIScrollView, using the delegate method scrollViewDidScroll:. The scrollView passed as a parameter can be used to check the contentOffset which is simply a CGPoint indicating how far into the content view of the UIScrollView the user has scrolled. Something like this can be used to relate the scroll position to the opacity of a given view in a paginated UIScrollView:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// The case where I used this, the x-coordinate was relevant. You may be concerned with the y-coordinate--I'm not sure
CGFloat percent = ((int)(scrollView.contentOffset.x) % (int)(scrollView.frame.size.width)) / scrollView.frame.size.width;
if (percent > 0.0 && percent < 1.0) { // Of course, you can specify your own range of alpha values
relevantView.alpha = percent; // You could also create a mathematical function that maps contentOffset to opacity in a different way than this
}
}
According to information that is still not supposed to be widely released, all iOS 4.x versions completely block user interaction while the animation is in progress.
Isn't it interesting, though, that you're UITouches are obviously still registered during the animation? Hmm... maybe that HINTS that something NEW is coming in a yet-to-be-released version!
I.e., If you can, read the iOS 5 Beta documentation on UIView class methods.

UIImageView Intro Animation issue

I am trying to create an intro animation for my iOS app and am having issues with timing. In particular I would like to change screens after the intro animation plays. I currently use a UIImageView and there does not appear to be a way to do this. Many stackoverflow questions say to use an NSTimer or performSelector:afterDelay but these are not accurate timers and in my case are completely wrong. Here is what I am doing.
Set UILaunchImageFile to LaunchImage.png
AppDelegate allocs an IntroViewController
IntroViewController.LoadView allocs IntroView
IntroView.initWithFrame performs the following
UIImageView* iv =
iv.animationImages =
iv.animationDuration = 2.0
iv.animationRepeatCount = 1
[iv startAnimating]
Set NSTimer/performSelector:afterDelay?
When timer triggers change from IntroViewController to something else.
If I perform either step 5 or 6 it does not work correctly. It does correctly play the animation and it will correctly change the view/view controller, but the timing is horribly horribly wrong. When you call startAnimating in this manner it may not actually start the animation for a full second or two. I presume because the app is still loading in resources somehow. This time however is not consistent across the simulator or all devices. Infact several runs on the same device may have different results. Thus I can not hard code some delay.
All I want to do is detect that a UIImageView animation has played the last frame and do something. That's it. The best solution I've found so far is to set a timer in some manner and then do something, but in my situation a timer is not a solution.
Any ideas?
The long delay you observe is due to reading and decoding the images, which UIImageView does before the animation begins.
Core Animation performs the animation for you, and it does its drawing in the render server, which is in a separate process. Remember that what you see on the screen doesn't necessarily represent your app's instantaneous picture of your layer tree: Core Animation Rendering Architecture.
UIImageView doesn't provide facilities to give you accurate results here. I'd suggest:
Make a UIView of your own.
Create a CAKeyframeAnimation with discrete calculation mode and your images' CGImageRefs as its values.
Set the animation's delegate to your IntroViewController.
Add the animation to your view's layer for the "contents" key.
Your IntroViewController will get animationDidStop:finished: when it's done.
Two things to consider, though:
First, you'll get better results using a movie rather than a series of images, since the movie can be streamed from storage.
Second, and more importantly, this solution will improve the timing situation but will not totally mitigate it. animationDidStop:finished: is called when your app thinks the animation is doneā€¦ which is not necessarily exactly when it appears to finish.
You'll do better if you don't rely on delegate callbacks for media timing: instead, add this animation and the animation transitioning your views (using a CAAnimationGroup if necessary) in the same turn of the run loop. Delay the latter with a beginTime of the first animation's duration. Depending on what you're doing, you may have to set the second animation's fill mode as well to get the correct behavior during the first.

How to cancel an animation for a specific context and animationID?

I have an animation which I kick off like this:
[UIView beginAnimations:#"doThis" context:self];
[UIView setAnimationDuration:1.5f];
[UIView setAnimationDelay:2.5f];
Now, the problem is that this animation is told to start in 2.5 seconds. But in the meantime, something may happen and I don't want the animation anymore. However, CA will just animate that thing after 2.5 seconds, no matter what happens. How could I say "no, thank you, don't animate"?
I have other animations going on in different context and animationID, so I don't want to just remove all animations from the app. What's the most clean way of achieving this? Just running another nonsense-animation with same context and animationID and old targets?
If you re-set the properties that are currently animating, that should kill the animation. If you have an animation delegate/didStopSelector set, the method will be called with kCFBooleanFalse as the "finished" parameter in this case.
There is another related question here:
Cancel a UIView animation?
One other thing you could do is to put your animation code in a method and then use:
[self performSelector:#selector(animateMethod) withObject:nil afterDelay:2.5];
Then you can add a check in the beginning of "animateMethod" to see if the animation should still be performed. This however does not help if you want to cancel the animation while it is running.