I have a method with three animation blocks, which animates two UIImageViews. Will this code cause a stack overflow exception in the future?
I mean recursive blocks. I need to animate them by order. The first big image should fade in, then a smaller image should fade in, and then both images should fade out, and animation should start again.
- (void)startAnimation {
[UIView animateWithDuration:0.3 animations:^{
[_bigImage setAlpha:1];
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[_mediumImage setAlpha:1];
}
completion:^(BOOL finished1) {
[UIView animateWithDuration:0.2 animations:^{
[_mediumImage setAlpha:0];
[_bigImage setAlpha:0];
}
completion:^(BOOL finished3) {
[self startAnimation];
}];
}];
}];
}
What you posted should work fine. Yes, you could also do it using CAAnimations, but those are harder to figure out.
I disagree with dasdom. I think your current approach is reasonable.
I do find nested animateWithDuration: methods to be hard to figure out. It's easy to get lost in the nested block syntax.
Instead, you could add an integer or enum instance variable that keeps track of the animation step, and have your method invoke different animation code for each step, then call itself again in each animation block:
In your header:
typedef enum
{
first,
second,
third,
done
} animationSteps
#interface MyClass
{
animationSteps currentStep
}
The .m file:
- (void)doAnimation
{
[UIView animateWithDuration:0.3 animations:^
{
switch (currentStep)
{
case first:
[_bigImage setAlpha:1];
break;
case second:
[_mediumImage setAlpha:1];
break;
case third:
[_mediumImage setAlpha:0];
[_bigImage setAlpha:0];
break;
}
}
completion:^(BOOL finished)
{
currentStep++;
if (currentStep == done)
currentStep = first;
[self doAnimation];
}
];
}
Related
I'm having a bit of trouble. I've set my UIView Animation to repeat itself infinitely, however it does this before the animation starts it's completion function. I want it to wait until after the completion function fully runs because I want to run multiple animations. Any idea how to do this? If not is there a different way I can accomplish the same thing?
I'll post my code below:
-(void) createBlueControlAnimation:(int)messageCount{
if(messageCount == 0) {
return;
}
[blueControl setContentOffset:CGPointMake(0, 0)];
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionRepeat
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth, 0)];
} completion:^(BOOL finished){
if(messageCount >= 2){
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth * 2, 0)];
} completion:^(BOOL finished){
if(messageCount == 3) {
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth * 3, 0)];
}completion:^(BOOL finished){
[self scrollAnimationToStart];
}];
}else{
[self scrollAnimationToStart];
}
}];
}else{
[self scrollAnimationToStart];
}
}];
}
-(void) scrollAnimationToStart {
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(0, 0)];
} completion:^(BOOL finished){}];
}
So based on the number of messages I want the animation to continue running, and then when it gets to the last message, loop back to the first message and then restart the animation. However right now it just loops infinitely between the first and the second message. Any ideas on how to fix this would be greatly appreciated, thanks.
Firstly: You should create separate consts for anything that is in code.
extern NSTimeInterval const animationDuration;
NSTimeInterval const animationDuration = 0.5;
extern NSTimeInterval const animationDelay;
NSTimeInterval const animationDelay = 2;
Secondly: intent your code in reasonable way, it's quite hard to read what you've pasted. Use one method and push every repeatable part of code into another method.
- (void)createBlueControlAnimation:(NSUInteger)messageCount {
if (messageCount == 0) {
return;
}
__weak typeof(self) weakSelf = self;
[self offsetBlueControl:0];
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth];
} completion:^(BOOL finished) {
if (messageCount < 2) {
[self scrollAnimationToStart];
} else {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth * 2];
} completion:^(BOOL finished) {
if (messageCount != 3) {
[self scrollAnimationToStart];
} else {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth * 3];
} completion:^(BOOL finished) {
[self scrollAnimationToStart];
}];
}
}];
}
}];
}
- (void)scrollAnimationToStart {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[blueControl setContentOffset:CGPointMake(0, 0)];
} completion:^(BOOL finished) {}];
}
- (void)offsetBlueControl:(CGFloat)xOffset {
[blueControl setContentOffset:CGPointMake(xOffset, 0)];
}
I should mention here that this animation block is in fact similar everywhere. I'd set it as a separate method also.
Thirdly, keep same way of spacing everywhere (eg check out https://github.com/NYTimes/objective-c-style-guide).
That's all about code, the quality, readibility etc. I'd set it as comment, but there's too much code in my response, so it has to be an answer.
I don't fully understand what do you want to do. As far as I understood:
You have a UI element, called BlueControl.
You want to move this element.
Basing on some counter you want to move your view by some width (btw I advise to switch to NSIntegers from ints and use unsigned option, so NSUInteger etc). The move steps:
3.1. Move button right during 0.5s
3.2. Wait 2 seconds if counter is large enough, otherwise end step 3
3.3. Increase counter, repeat step 3.
You want to repeat the process infinitely.
Is that right? I need to understand the problem to edit this answer and paste a full response here.
I want to animate the text of a UILabel so that it shows one text for a couple of seconds and after that fades into a default text.
I'm currently using the following code:
-(void)tapOnBalance:(id)sender{
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
cell.amountLabel.text = #"Hola!";
} completion:nil];
// Pause the function - act as a 'delay'
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
cell.amountLabel.text = #"Hallo!";
} completion:nil];
}
This works, but the [NSRunLoop currentRunLoop] is pausing the entire app, blocking everything for 3 seconds.
How do I get rid of the block on the main thread and still get the same result?
A simple fade (not a cross-fade) can go like this:
// change a label's text after some delay by fading
// out, changing the text, then fading back in
- (void)fadeLabel:(UILabel *)label toText:(NSString *)toText completion:(void (^)(BOOL))completion {
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
label.alpha = 0.0;
} completion:^(BOOL finished) {
label.text = toText;
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
label.alpha = 1.0;
} completion:completion];
}];
}
// simpler signature that can be done on a perform
- (void)changeLabelText:(NSString *)toText {
[self fadeLabel:self.myLabel toText:toText completion:nil];
}
// set the label to something, then change it with a fade, then change it back
self.myLabel.text = #"text";
[self performSelector:#selector(changeLabelText:) withObject:#"hola!" afterDelay:4];
[self performSelector:#selector(changeLabelText:) withObject:#"text" afterDelay:8];
You could also nest the calls' completion blocks (and add a delay param) to do something similar without the performs. To do a cross-fade, apply a similar technique with two labels (that have the same frame) where one's alpha is zero and the other's alpha is one.
I new to IOS programming.
I want to toggle button border color automatically at start of apps to get user attention,
I have tried the below code, but only the final color is selected.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.0];
[UIView setAnimationDuration:3.0];
button.layer.borderWidth=2.5f;
button.layer.borderColor=[[UIColor blueColor]CGColor];
[UIView setAnimationDelay:3.0];
button.layer.borderColor=[[UIColor clearColor]CGColor];
[UIView setAnimationDelay:6.0];
button.layer.borderColor=[[UIColor redColor]CGColor];
[UIView commitAnimations];
}
What you're doing is an invalid way to chain animations. As a result, only the last changes are applied. Additionally, you should be using block based animations, which Apple has recommended since iOS 4. Should be something like this:
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderWidth=2.5f;
button.layer.borderColor=[[UIColor blueColor]CGColor];
} completion:^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor clearColor]CGColor];
} completion:^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor redColor]CGColor];
}];
}];
}];
This answer is the same as 0x7fffffff's answer, except it uses block variables so it looks a little cleaner and hopefully makes more sense:
void (^animateToRed)(BOOL finished) = ^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor redColor]CGColor];
} completion: nil];
}
void (^animateToClear)(BOOL finished) = ^(BOOL finished) {
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderColor=[[UIColor clearColor]CGColor];
} completion:animateToRed];
}
[UIView animateWithDuration:3.0 animations:^{
button.layer.borderWidth=2.5f;
button.layer.borderColor=[[UIColor blueColor]CGColor];
} completion:animateToClear];
UIView's animateWithDuration:animations:completion: method is the best way to animate changes over time.
It takes 3 arguments.
A duration as a CGFloat, which is a measure of the length of the animation in seconds.
An animation block, which tells what animations to perform.
A completion block, which allows you to execute code after the animation is complete.
This code snippet creates two completion blocks.
The animateToRed completion block handles the animating of the border to red. It's completion block is nil, at this point, we're done animating.
The animateToClear completion block handles the animating of the border to clear. It's completion block is the animateToRed which we just defined.
Finally, we call animateWithDuration, animating the border to blue, and passing the animateToClear block for the completion (which in turn calls the animateToRed block).
For an animation this simple and with no repeated animations, it may seem like slightly overkill to do it this way (though it is slightly more readable). However, with a more complicated series of animations, especially if there's any repetitiveness, creating block variables like this to use and pass quickly becomes quite helpful.
I want an animated UIView that will move like this lever on this image. seesaw but i have no great knowledge on animations in xcode. I can only do sliding like this
[UIView animateWithDuration:1.0 delay:2.0 options:(UIViewAnimationCurveEaseInOut|UIViewAnimationOptionAllowUserInteraction)
animations:^
{
[UIView setAnimationDelegate:self];
self.bug.center = CGPointMake(75, 200);
}
completion:^(BOOL finished)
{
NSLog(#"Move to left done");
}
];
Is there a way that i can use manipulate this code and animate it like a seesaw?
I've only worked a bit with UIView animations and blocks, basically one step animations. I'd like to stack a few steps into a sequence. The code below seems to be working but I'm wondering if this is the right approach and/or if there are any issues/limitations with placing blocks within blocks.
Blocks seem great though the code formatting gets sort of unwieldy/unreadable.
CGRect newFrame = CGRectMake(0, 0, 500, 500);
UIView *flashView = [[UIView alloc] initWithFrame:newFrame];
flashView.tag = 999;
[flashView setBackgroundColor:[UIColor grayColor]];
[flashView setAlpha:0.f];
[self.view addSubview:flashView];
[UIView animateWithDuration:.2f
animations:^{
// STEP 1: FADE IN
[flashView setAlpha:1.f];
}
completion:^(BOOL finished){
[UIView animateWithDuration:.9f
animations:^{
// STEP 2: FADE OUT
[flashView setAlpha:0.f];
}
completion:^(BOOL finished){
// STEP 3: CLEAN UP
[flashView removeFromSuperview];
}
];
}];
What you're doing is correct. Sometimes the nesting can get ugly. One alternative for readability is to put each animation in its own method:
-(IBAction)someButtonPressed:(id)sender {
[self saveSomeData];
[self fadeInAndOut];
}
-(void)fadeInAndOut {
[UIView animateWithDuration:.2f
animations:^{
// STEP 1: FADE IN
[self.flashView setAlpha:1.f];
}
completion:[self fadeOut]
];
}
-(void (^)(BOOL))fadeOut {
return ^(BOOL finished) {
[UIView animateWithDuration:.9f
animations:^{
// STEP 2: FADE OUT
[self.flashView setAlpha:0.f];
}
completion:[self cleanUpFlashView]
];
};
}
-(void (^)(BOOL))cleanUpFlashView {
return ^(BOOL finished){
// STEP 3: CLEAN UP
[self.flashView removeFromSuperview];
};
}
That way isn't horrible if you're just nesting once with simple code. If you're going to do something more complex you might try animateWithDuration:delay:options:animations:completion:
using delay: as a way to link the animations. For example:
[UIView animateWithDuration:.2f delay:0
options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionAllowUserInteraction
animations:^{
// STEP 1: FADE IN
[flashView setAlpha:1.f];
}
completion:nil
];
[UIView animateWithDuration:.9f delay:.2f
options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionAllowUserInteraction
animations:^{
// STEP 2: FADE OUT
[flashView setAlpha:0.f];
}
completion:^(BOOL finished){
// STEP 3: CLEAN UP
[flashView removeFromSuperview];
}
];