Canceling an UIView Animation Block - objective-c

The code below shows an animation of a label which contains a status message for the user. If an event happens the label is showing prompt and is slowly disappearing via uiview animation block.
- (void)showStatusOnLabelWithString:(NSString *)statusMessage
{
// [self.view.layer removeAllAnimations]; // not working
[labelStatus.layer removeAllAnimations]; // not working, too
[labelStatus setText:statusMessage];
[labelStatus setHidden:NO];
[labelStatus setAlpha:1.0];
[UIView animateWithDuration:5.0 animations:^
{
[labelStatus setAlpha:0.0];
} completion:^(BOOL finished)
{
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}];
}
If there is another event in the following 5s after the first the label should animate again, so I removed the previous animation with [self.view.layer removeAllAnimations] (thats what I thought).
But the label just completely disappear and the next 5s the label is invisible again.
If I (or the user) wait(s) the 5s everything is working properly.
Why isn't this working?
Kind Regards,
$h#rky

Change this:
completion:^(BOOL finished)
{
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}];
to this:
completion:^(BOOL finished)
{
if (finished) {
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}
}];
The reason is you are reaching this completion block when you remove the animations for the layer, but finished will be false because you interrupted it. Also, the order is important here. Perhaps you were expecting removeAllAnimations to call the completion block instantly, but instead it will be called after your showStatusOnLabelWithString: method finishes, so what is happening is you are calling setHidden:NO followed immediately by setHidden:YES.

Did you try removing animations from the label's layer (labelStatus.layer)?

Related

Remove from superview not working in animation completion handler

I have a UIView with several UILabels added. I am simply moving them all to the center of the screen with an animation, and then attempting to remove them from their superview in the animation completion handler.
for (label in [self.view subviews])
{
if([label isKindOfClass:[UILabel class]])
{
CGRect frame = CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/2, label.frame.size.width, label.frame.size.height);
[UIView animateWithDuration:2.0
delay:0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
[self->label setFrame:frame];
}
completion:^(BOOL finished){
dispatch_async(dispatch_get_main_queue(),^{
[self->label removeFromSuperview];
});
}
];
}
}
The problem that I am having is that at the end of the animation the UILabels remain. If I put the removeFromSuperView call outside of the animation block then it works, but of course then they are removed before the animation has a chance to complete.
You've got label as the variable in the for-in and self->label in the blocks. Apparently, you weren't operating on the label you thought you were.

Setting UIButton's title cancels animation

I am not familiar with iOS animation, and here is my problem:
On the login screen of our app, there is a login button. When that button is clicked, we need to move it up then change its title. Moving it up is animated like this:
[UIView animateWithDuration:0.5
animations:^{
loginButton.frame = newLoginButtonFrame;
}
completion:^ (BOOL finished) {
}];
This works as expected.
Then after we try to change the title in the completion callback like this:
[UIView animateWithDuration:0.5
animations:^{
loginButton.frame = newLoginButtonFrame;
}
completion:^ (BOOL finished) {
[loginButton setTitle:#"Cancel" forState:UIControlStateNormal];
}];
something weird happens: the button "jumps" back to the original position as if the animation was cancelled.
What is going on here?
If you are using auto layout you should animate the constraints not the frame. For example you can set an outlet for the bottom constraint and animate like this:
viewBottomConstraint.constant = 32;
[UIView animateWithDuration:0.5
animations:^{
[self.view layoutIfNeeded]; // Called on parent view
}];
}
You can find more information in the documentation here

Animating with Auto Layout and iOS7 jumps to end of animation (fine in iOS6)

I am using Autolayout and animating by changing the constraints, however, in iOS7 the view simply jumps to the end position - in iOS6 I get a nice animation.
Is should be noted these views are UICollectionViews and I have checked the Storyboard and there are no Layout errors.
All I can think is there is something and am or am not setting on the Storyboard or something that I am doing wrong with the Constant settings in the Storyboard.
primaryMenuYContraints.constant = BUTTOMX;;
leftMenuYContraints.constant = 136.0f;
leftMenuBottomConstraint.constant = 5.0f;
[UIView animateWithDuration:0.7f
delay:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^
{
// Move in menus
[self.primaryOptionCollection layoutIfNeeded];
[self.menuOptionCollection layoutIfNeeded];
}
completion:^(BOOL finished)
{
}];
I changed to and now works in both iOS7 and 6, still not sure why it does/did it though! I still think I am setting something up wrong in the Storyboard. I am add another view (nothing to do with this lot) programmatically so I believe that is based around frames until I convert it (which I am not doing).
primaryMenuYContraints.constant = BUTTOMX;;
leftMenuYContraints.constant = 136.0f;
leftMenuBottomConstraint.constant = 5.0f;
[UIView animateWithDuration:0.7f
delay:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^
{
// Move in menus
[self.view layoutIfNeeded];
}
completion:^(BOOL finished)
{
}];

Alpha changing instantly with UIView animateWithDuration: instead of animating

I'm attempting to fade in a UIView that I've created by animating the alpha. I need to fade it in, leave it visible for a few seconds, then fade it out.
The fade out function works fine. The view smoothly disappears. But the fade in just makes the view appear instantly instead of slowly appearing over an interval of 0.5 seconds.
So it seems like the fade in animation isn't working, just instantly setting the alpha to 1.0. I'm kind of at a loss here. Any ideas what I'm doing wrong? Thanks!
-(void)presentPopupPhrase:(NSString *)phrase
inView:(UIView *)view
withDelegate:(id)delegate
andCompletion:(void (^)(BOOL completed))completion {
MessagePopupView *pv = [[[MessagePopupView alloc] initWithFrame:self.frame andText:phrase] autorelease];
pv.alpha = 0.0;
[view addSubview:pv];
[self fadeInMPV:pv
withDuration:self.fadeDuration
andDelay:self.fadeInDelay];
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
}
-(void)fadeInMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
andDelay:(NSTimeInterval)delay
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 1.0;
}
completion:nil];
}
-(void)fadeOutMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
afterDelay:(NSTimeInterval)delay
withCompletion:(void (^)(BOOL completed))completion
andDelegate:(id)delegate
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 0.0;
}
completion:completion];
}
EDIT:
If it helps, here's the VC code where I'm calling it from:
-(void)viewDidAppear:(BOOL)animated {
CGRect phraseFrame = CGRectMake(20, 341, 280, 65);
PopupPhraseController *phraseController = [[[PopupPhraseController alloc] initWithFrame:phraseFrame] autorelease];
[phraseController presentPopupPhrase:#"Test Phrase" inView:self.view withDelegate:self andCompletion:^(BOOL completed){
if (completed) {
NSLog(#"completed");
} else {
NSLog(#"not completed");
}
NSLog(#"blocked!");
}];
[super viewDidAppear:animated];
}
You can't chain the animations like that as the second animation block essentially causes the first one to be cancelled out.
You have two options for Linking Multiple Animations Together:
Use the completion handler of the first animation
Nest the animations but delay the second one
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
}];
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay + self.fadeDuration
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
} completion:nil];
The issue is because of the way you are setting up the animations.
When you set animatable properties on a view the backing model of the view is updated instantly. So when you set the alpha = 1.0 in the first block the backing data model for the view has the alpha as 1.0 and then the actual animation is kicked off on another thread to show the interpolation between the two values.
When you define the second block straight after the first the view essentially says "ok I need to animate from 1.0 (my current value in the model) to 0.0", which is why you don't see what you expected.
Thanks to Paul.s for dropping the hint there. I think this expected behaviour is a little weird, even if it seems logical from a low level perspective.
To expand on Paul's 2 solutions, there is a 3rd solution which is better IMO.
Use GCD to add a delay to the fade out code, like such:
//Make sure this delay exceeds your fade-in time...
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
});
This solution's better because the executing view controller has complete control on not only when, but what gets to trigger the fade out (for example, a user action).

Different Objective C syntax for functiona call

[UIView animateWithDuration:1.0 animations:^(void) {
im.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0 delay:1.0 options:UIViewAnimationCurveEaseOut animations:^(void) {
im.alpha = 0.0;
} completion:^(BOOL finished) {
[im removeFromSuperview];
}];
}];
The code is for animating UIImageView, that I know;
I want to know the calling mechanism, as I haven't seen this kind of function call for the very first time.
Mainly, What is ^(void) and why im.transform = CGAffineTransformIdentity; passed to it?
I have gone thorugh Apple docs, to find anything related to this function call, and I got it too, but I didn't get any idea from there; or I might have been to wrong section.
Can anyone here guide me to this?
This is called a block, and was introduced in iOS 4 and Mac OS X 10.6.
Here are some links where you can learn more about them:
Apple's Documentation - Blocks Programming Topics
Ask Big Nerd Ranch: Blocks in Objective-C
Programming with C Blocks
Introduction to Blocks in Objective-C – Part 1
The above example should read as follows:
// Start an animation over the next 1 second
[UIView animateWithDuration:1.0 animations:^(void) {
// For this animation, animate from the current value of im.transform back to the identity transform
im.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) { // At the completion of the first animation...
// Wait 1 second, then start another 1-second long animation
[UIView animateWithDuration:1.0 delay:1.0 options:UIViewAnimationCurveEaseOut animations:^(void) {
im.alpha = 0.0; // Fade out im during this animation
} completion:^(BOOL finished) { // When you complete this second animation
[im removeFromSuperview]; // Remove im from its superview
}];
}];
So you will have one second where im animates the removal of its transform, a one second delay, and then a one second fadeout of im.