I have a priority queue of various actions -- UIImageView translations and the HP bar decreases associated with them. I already programmed a bar widget with CALayer animations, so it should handle itself, but for some reason, even though my loop is sequential, all the animations -- bar animations as well as other actions in the queue -- all happen simultaneously. How do I force sequentiality? Here's code from my viewController .m:
while (![selectedMoves isEmpty])
{
SelectedMove* nextMove = [selectedMoves dequeueMax];
// Move the user to clearly indicate who is moving:
[self animateAttacker:nextMove.user];
[moveReporter setText:report];
[self executeMove:nextMove];
}
executeMove is the wrapper that changes the health bar value to reflect damage.
-(void) executeMove:(SelectedMove*)attack
{
... calculate damage dealt by attack...
// Animate HP bar change on-screen
double percentage = (double)[currentHP intValue] / (double)[currentStats[HP] intValue];
[playerHealthBars[slot] updateBarPercentage:percentage andMaxHP:[currentStats[HP] intValue]];
}
My issue is that all of the healthBar animations, as well as the "animateAttacker" calls, are happening at once on the screen. What command forces one to execute before the other, or forces a screen refresh before moving on?
animateAttacker is the wrapper that translates the appropriate UIImage forward according to nextMove.
The goal is to have each iteration of the loop -- the health bar and the animateAttacker -- execute simultaneously, or sequentially, and to have separate iterations execute in the order of the loop:
:
-(void)animateAttacker:(HonmonBattleState*)attacker
{
UIImageView* attackerImageView = playerImages[index];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
attackerImageView.transform=CGAffineTransformMakeTranslation(ATTACK_PERTURBATION, -ATTACK_PERTURBATION);
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
attackerImageView.transform=CGAffineTransformIdentity;
[UIView commitAnimations];
}
Thanks for your help!
PS> I can use CALayer Core Animations or UIView animations. I have done both, and the health bars are internally done using layers.
Block-based UIView animations have completion blocks that you can use to chain animations, see this thread over here.
And you can use completion blocks in CAAnimations with this category on GitHub (it's on CocoaPods if you need).
If you want a global, serial queue for your animations, you should look into having your own NSOperationQueue with a maximum parallel task count of one. This way, all operations you run on it will be delayed until the previous ones have finished.
Related
I have method -(void)animationStart that consist a lot of [self performSelector:#selector(myAnimation) withObject:nil afterDelay: 21 * 0.01]; with different method's and delays.
How can I refactor my code not to use performSelector? I use it for make animation consequentially change.
You don't specify how you exactly perform the animations themselves, but with UIView animation blocks you can also specify a delay.(animateWithDuration:delay:options:animations:completion:).
Alternatively you can use NSTimerfor performing delays.
But for a detailed answer you should provide more details on how you perform your animation.
Update
Using animation blocks is very straightforward...In the completion block (see code below) you can start the follow up animation (for series of animations). For parallel animation processing you just start multiple blocks of animations...there are different messages. If you don't need to handle the animation end, you can use the ones without completion block.
Here is an example:
[UIView animateWithDuration:2.0 delay:0.1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
// perform animation code here
} completion:^(BOOL finished) {
// This code is performed after the animation is completed. E.g. you can start a new animation here
// (for serial animation processing):
}];
I'm trying to implement multistage animation using UIViewAnimationOptionBeginFromCurrentState to allow the user to cancel the animation at will. The animation is a view that continually and cyclically animates between two sizes. When the user touches the view to cancel the animation, I want the view to quickly revert back to its original, small size, whether it was growing or shrinking at the time.
I'm implementing the multistaging aspect by having two separate animations, one for growing the view and one for shrinking it. Each calls the other routine in its completion block, thus cycling forever unless the abort flag has been set.
I get the expected behaviour if abort is called during the grow-the-view animation: the animation quickly and immediately returns the view to its original, small size and stops. Good!
However, if abort is called during the shrink-the-view animation cycle, the view continues to shrink at the same speed (and then stops as expected), as if the UIViewAnimationOptionBeginFromCurrentState option was never invoked.
Code will hopefully make this clearer and hopefully somebody can see what I can't.
- (void)stopAnimating {
abort = YES;
[UIView animateWithDuration:.2 // some small interval
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ self.frame = minRect;}
completion:^(BOOL done){}
];
}
- (void)animateSmall {
[UIView animateWithDuration:4
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{self.frame = minRect;}
completion:^(BOOL done){if (!abort)[self animateBig];}
];
}
- (void)animateBig {
[UIView animateWithDuration:4
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{self.frame = maxRect;}
completion:^(BOOL done){if (!abort)[self animateSmall];}
];
}
Just a guess here, because your code looks exactly as I would have done it. But I think what's going on is that the abort animation is setting the same attribute to the same value as the animation it's interrupting, and this gets treated as equivalent and not in need of change (even though the duration changes).
A test of this theory - and a fix to the problem - would be to make your oscillating minRect just a little bit different in size than your steady state minRect.
Hope this works. Good luck.
I have two animations running; show search bar and show banner. Both those animations are resizing the same view and if they are running on the same time (Which they are) the latest animation will cancel the resize. Is there anyway to check if UIView is currently animating and then standby for animation?
I'm pretty sure I'm not using CAAnimations, since Cocoa not is detecting such class.
This one is running when an ad was received. Unfortunately, that is the same time as ShowSearch is running.
- (void)adViewDidReceiveAd:(GADBannerView *)bannerView {
if (!hasloaded) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration: 1.0];
[bannerView_ setFrame:CGRectMake(0, self.view.frame.size.height, bannerView_.frame.size.width, bannerView_.frame.size.height)];
// move and grow
[bannerView_ setFrame:CGRectMake(0, self.view.frame.size.height-50, bannerView_.frame.size.width, bannerView_.frame.size.height)];
// set original position
[UIT setFrame:CGRectMake(UIT.frame.origin.x, UIT.frame.origin.y, UIT.frame.size.width, UIT.frame.size.height)];
// move and grow
[UIT setFrame:CGRectMake(UIT.frame.origin.x, UIT.frame.origin.y, UIT.frame.size.width, UIT.frame.size.height-50)];
[UIView commitAnimations];
hasloaded = true;
}
}
You can use the completion block in the UIView method +animateWithDuration:animations:completion: (which is a more modern alternative to beginAnimations/commitAnimations) to chain multiple animations (I'm guessing this is what you want to do?).
If you select your code while entering it and press control + K, you will preserve formatting and make it pretty. Try it. Reading the wall of text made from pasting code into a true-type non-color-formatted environment.
Nick Weaver says:
A UIView has a layer (CALayer). You can send animationKeys to it which will give you an array of keys which identify the animations attached to the layer. I suppose that if there are any entries, the animation(s) are running. If you want to dig even deeper have a look at the CAMediaTiming protocol which CALayer adopts. It does some more information on the current animation.
I'm afraid that the title is correct english expression but,
I have an animation on iOS with 4 scenes.
I used setAnimationDidStopSelector: method.
Here's the question: How can I make stopping function for the animation?
In iOS 4 and later you should use block based animation. Using these newer methods you can very easily specify some code to run once the animation has finished. For example:
[UIView animateWithDuration:1.0
animations:^{
// This code will be animated for 1 second.
[anObject setAlpha:0.0];
}
completion:^(BOOL finished) {
// This code will be executed once the animation has completed.
[anObject removeFromSuperview];
}];
A little ugly, but pretty lazy:
add a flag to know when it should animate, and when not. Put your animation under if block (or something same) and just switch flag when you need.
Why does this not work?
[UIView animateWithDuration:0 delay:3 options:0 animations:^(void) {
NSLog(#"after duration, please!");
} completion:nil];
The NSLog fires immediately.
I'm not looking for workarounds (there are many), but rather wondering why this would be.
The intermediate frames of core animation are actually happening in another background thread. The first and last frame of the animation are often created immediately. So the NSLog can fire in the main UI thread without waiting or even knowing about any background animation in progress.