Objective-C- animateWithDuration Completion block called early - objective-c

Before you discredit this question as being asked before, please look at the details:
I have a simple animation of shrinking the frame of a view from it's normal size to a zero frame in the middle of the superview. In my completion block I remove the superview from its superview and remove the view controller from the parent view controller. The completion block is called immediately. Moreover, the BOOL parameter finished to the completion block equals YES. So when I check if the animation is finished it says that it is even when it's not and the view is prematurely removed. Here is my code:
- (void)closeWindow {
[UIView animateWithDuration:0.5
animations:^{
contentView.frame = [self getSchoolSetUpStartingFrame];
} completion:^(BOOL finished){
if(finished) {
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
}];
}
- (CGRect)getSchoolSetUpStartingFrame {
CGRect startingFrame;
CGRect myFrame = self.view.frame;
float xPos = (myFrame.origin.x + (myFrame.size.width/2));
float yPos = (myFrame.origin.y + (myFrame.size.height/2));
startingFrame = CGRectMake(xPos, yPos, 0, 0);
return startingFrame;
}
Is there anything else that could be related to this problem that I haven't considered yet? I appreciate all the help.

I'd try changing the animation duration to .5f, and verify that contentView is not nil during the animation block.
Also, it might be easier to achieve this animation by using a transform
animations:^{
contentView.transform = CGAffineTransformMakeScale(.1f, .1f);
}

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.

objective-c animations - how to set end position, direction and type

I've seen many examples and read tutorial on animations,
all examples look +- the same like so:
- (void)showAnimation
{
[UIView animateWithDuration:0.3f animations:^{
backgroundView.alpha = 1.0f;
}];
[UIView animateWithDuration:1.0f
delay:0.0f
usingSpringWithDamping:0.4f
initialSpringVelocity:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^{
CATransform3D init = CATransform3DIdentity;
alertView.layer.transform = init;
}
completion:^(BOOL finished) {
if( [self.delegate respondsToSelector:#selector(alertViewDidAppear:)] && finished) {
[self.delegate alertViewDidAppear:self];
}
}];
}
what I'm failing to understand is in the animation block where:
1. I set the beginning position
2. set the end position
3. the direction of movement
4. type of animation (fly-in/ turn/ fade-in/appear etc.)
There is no "starting position". There is no "direction of movement" or "type of animation". Animation is a change over time. The view is a certain way at the time your code runs. UIView animation has 6 possible view properties. You change any of those in the animation block (which states the time) and the change is animated - that is, instead of the change just happening, kaboom, it is performed over the given time.
That's all there is to it (as far as UIView class-method animation is concerned).

Presented ViewController ignores UIView animation

I have a simple animation that moves a slide up and down repeatedly. Here is the code:
CGRect frm_down = slider.frame;
frm_down.origin.y += 50;
[UIView animateWithDuration:0.7
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
slider.frame = frm_down;
}
completion:NULL];
This is pretty straight forward and works great; however, when I am presenting the view (self presentViewController:animated:) the animation does not happen.
In fact, the slider is INSTANTLY moved to the lower position (as if it called the animation only once and moved it back down, but not back up).
I have also tried this:
CGRect frm_down = [[[slider layer] presentationLayer] frame]
This will keep the slider in its original position instead of moving down once, and the animation still does not happen.
Any idea what I am missing? Thanks!
Your animation works. I tested it.
What you need is:
Instead of using:
self presentViewController:animated:
Use this:
[self addChildViewController:<YOUR_VIEWCONTROLLER>];
[self.view addSubview:<YOUR_VIEWCONTROLER>.view];
[<YOUR_VIEWCONTROLER> didMoveToParentViewController:self];
Addendum:
In your view controller's viewDidAppear, add the following code:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGRect frm_down = self.view.frame;
frm_down.origin.y += 50;
[UIView animateWithDuration:0.7
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
self.view.frame = frm_down;
}
completion:NULL];
}
It will animate when it comes into view.
I ran it as a test a priori. Hope it helps.
On Swift :
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
var from_down = self.view.frame
from_down.origin.y += 50
UIView.animate(withDuration: 0.2, delay: 0.0, options: .autoreverse, animations: {
self.view.frame = from_down
}, completion: nil)
}

Scaling UIView using transform after animating frame change causes UIView to jump back to original frame before scaling

I'm trying to scale a UIView (with animation) after I move it (with animation). The problem is, when the scaling animation begins, it jumps back to the original position. Why?
[UIView animateWithDuration:t delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
// Drop the ball
CGRect frame = coinView.frame;
frame.origin.y += d;
coinView.frame = frame;
coinView.shouldSparkle = NO;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
// Initial scale up for ball "poof"
coinView.transform = CGAffineTransformScale(coinView.transform, 1.5, 1.5);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
coinView.transform = CGAffineTransformScale(coinView.transform, 0.000001, 0.000001);
} completion:^(BOOL finished) {
[coinView removeFromSuperview];
}];
}];
}];
EDIT: This is how I generated my d:
static CGFloat groundPositionY = 325;
CGRect convertedFrame = [coinView.superview convertRect:coinView.frame toView:self.view];
CGFloat d = groundPositionY - CGRectGetMaxY(convertedFrame);
EDIT2: Okay, so I changed the second UIView animation to the following and I discovered that the jump (and the scale down) happens before the second animation occurs, i.e. when animateWithDuration:delay:options:animations:completion: is called.
[UIView animateWithDuration:5 delay:3 options:0 animations:^{
coinView.transform = CGAffineTransformScale(coinView.transform, 1.5, 1.5);
} completion:nil];
I tested your code and it works fine for me. How do you generate your d? and on which block exactly it goes back?
I found the problem...
The coinView is a subview of a child view controller's view. The problem comes from the fact that I overrode that child view controller's viewDidLayoutSubviews method to lay out all the coinView's, so whenever that method was called, the coin view would move back to its original position and size intended by the child view controller.
Thanks for all of your help, repoguy and sergio!!

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).