Stop specific UIViewAnimation block - iOS - ios

I have an iOS application which runs a few different UIViewAnimation blocks to animate a few different objects on screen. This all works, but how can I stop ONE of the animation blocks WITHOUT stopping the rest?
I have tried using the following method:
[button.layer removeAllAnimations];
But it doesn't do anything, the animation just continues.
I then tried to use a simple BOOL value and get the animation to return from the method once the BOOL is set to "NO", but that doesn't work either.
Here is the animation which I am trying to stop:
-(void)undo_animation:(int)num {
// Fade out/in the number button label
// animation which lets the user know
// they can undo this particular action.
[UIView animateWithDuration:0.5 delay:0.2 options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction) animations:^{
// Mostly fade out the number button label.
((UIButton *)_buttons[num]).alpha = 0.2;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 delay:0.2 options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction) animations:^{
// Fade in the number button label.
((UIButton *)_buttons[num]).alpha = 1.0;
} completion:^(BOOL finished) {
// Stop the animation if the BOOL
// is set to 'NO' animations.
if (anim_state_button == NO) {
return;
}
}];
}];
}
Thanks, Dan.

You can't reach running animation property if you're using UIKit Animations. So I suggest to using Core Animation if you want to modify animation in the runtime of it.
And it's too simple to removing alpha of the view like below.
CABasicAnimation* fadein= [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
[[moviepic layer]addAnimation:fadein forKey:#"MyAnimation"];
after adding animation to layer animation will start and than you can use delegate methods to be informed about animationDidFinish: method
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
NSLog(#"Animation interrupted: %#", (!flag)?#"Yes" : #"No");
}
Also you can reach whenever you want from using;
[[moviepic layer] animationForKey:#"MyAnimation"];
And of course you need to add CoreAnimation Framework to your project.
Hope it helps.

I think the simple way is that remove all animations from your View Layer as all the animation are by default added into the View's Layer.
[yourRequiredView.layer removeAllAnimations];

My understanding is that removing all animations from the relevant layer should stop all animations. How about [((UIButton *)_buttons[num]).layer removeAllAnimations]; ?

Related

Force UIView animateWithDuration completion block to be called

I have two animations which are overlapping, which, because of the way I've set my method up, causes the second one not to fire. I have a check like so in the beginning of the method:
- (void)animateHidden:(BOOL)hidden duration:(CGFloat)seconds delay:(CGFloat)delay options:(UIViewAnimationOptions)options disableUserInteraction:(BOOL)disableUserInteraction {
if (self.hidden == hidden) {
return;
}
Then, further down, my animation block looks like so:
__weak UIView *weakSelf = self;
[UIView animateWithDuration:seconds delay:delay options:options animations:^{
weakSelf.alpha = hidden ? 0 : 1;
} completion:^(BOOL finished) {
// Return user interaction to previous state
if (disableUserInteraction) {
weakSelf.userInteractionEnabled = userInteractionEnabled;
}
weakSelf.hidden = hidden;
}];
Two animations are kicked off on the same view, one before a service call and one after. If the service call happens quick enough that the view is still animating, weakSelf.hidden = hidden; will never be called, and the second animation will exit out since the hidden value wasn't updated in time.
Is there anyway that I could force the completion block on the animation block to be called? I need to update my hidden property before making the check, but can't find a way to accomplish this.
Calling [self.layer removeAllAnimations] doesn't seem to work unfortunately.
You can use CABasicAnimation instead of UIView animation, that will solve the problem more accurately.
You can use it like:
CABasicAnimation* opacityZero= [CABasicAnimation animationWithKeyPath:#"opacity"];
[opacityZero setToValue:[NSNumber numberWithFloat:0.0]];
[opacityZero setDuration:duration];
[[self layer] addAnimation:opacityZero forKey:#"opacityZero"];
And when your service call ends, you can call [self.layer removeAllAnimations];
Similarly, you can make the opacity one and tweak the above method as you like.
You can find more info here.
A __block prefixed to hidden attribute declaration should help.
Something like, #property (nonatomic) __block BOOL hidden;
If you are targeting iOS 10+ take a look at UIViewPropertyAnimator
https://developer.apple.com/reference/uikit/uiviewpropertyanimator?language=objc
Combined with the UIViewAnimating and UIViewImplicitlyAnimating protocols, this allows modification / interruption / pause / resume / stop / etc of the animations.
Basic example (buttons and view set in IB):
- (IBAction)startTapped:(id)sender {
_myAnimator = [UIViewPropertyAnimator
runningPropertyAnimatorWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
_theRedBox.alpha = _theRedBox.alpha > 0 ? 0 : 1;
} completion:^(UIViewAnimatingPosition finalPosition) {
// do stuff
}];
}
- (IBAction)stopTapped:(id)sender {
[_myAnimator stopAnimation:NO];
[_myAnimator finishAnimationAtPosition:UIViewAnimatingPositionEnd];
}

Pausing a UIView's key frame animations

I'm currently creating a loading bar that I would like to pause and reverse if a specific action is taken.
I have the following code below that animates one view in another. How would I go about pausing this animation and applying another animation on the CURRENT frame at that point in time for the animated object. I would like to be able to animate this bar down to the 0 width mark at any given time within the current animation.
-(void)animateProgressBar
{
CGRect endingFrame = self.frame;
[UIView animateKeyframesWithDuration:self.time delay:0.0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:1.0 animations:^{
self.loadingBar.frame = endingFrame;
}];
} completion:^(BOOL finished) {
NSLog(#"TADA");
}];
}
If you want to start a new animation from where this one is now, just start the new animation using the the UIViewKeyframeAnimationOptionBeginFromCurrentState option (or if just doing animateWithDuration, use UIViewAnimationOptionBeginFromCurrentState).
You can import QuartzCore framework, add
#import <QuartzCore/QuartzCore.h>
And you can remove existing animation by calling:
[self.loadingBar.layer removeAllAnimations];
After that you can start new animation.
If you want to pause it you have to get reference to the current animation:
CALayer *currentLayer = self.loadingBar.layer.presentationLayer;
And you have to save it:
self.loadingBar.layer.transform = currentLayer.transform;
You can do it in another [UIView animateWith..... method.

iOS Graphic Appear and Fade Out with Alpha

I've been reading about UIView animateWithDuration which I'm trying to use so when a button is pressed a graphic appears then slowly fades out (i.e. alpha is set to 0).
I'm using the code below in my viewdidload just for test purposes however its not working:
[UIView animateWithDuration:10 animations:^{
self.completeImage.alpha = 1.0;
self.completeImage.alpha = 0.5;
self.completeImage.alpha = 0.0;
}];
Any ideas?
Thanks.
That is not working because automatically it sets the alpha to 0.0;
The 3 lines of code are executed at the same time (one after the other).
The proper way to use the UView animation block it is like this:
self.completeImage.alpha = 0.0;
[UIView animateWithDuration:2.0
animations:^{
// do first animation
self.completeImage.alpha = 1.0;
}
completion:^(BOOL finished){
[UIView animateWithDuration:2.0
animations:^{
// do second animation
self.completeImage.alpha = 0.0;
}
completion:^(BOOL finished){
;
}];
}];
Hope this achieve what you are looking for.
In addition:
" I'm trying to use so when a button is pressed a graphic appears
then slowly fades out (i.e. alpha is set to 0)."
As per your above information in the question, addition of the code in viewDidLoad will not prove fruitful. You need to add this code in the action target method of your button in order to play the animation on click of a button. Generally if you're using the nib, then the action method will be like below:
-(IBAction)on_pressing_my_button:(id)sender
{
///your animation code goes here..
}

How to stop and reverse a UIView animation?

I have animated a UIView so that it shrinks when the user touches a toggle button and it expands back to its original size when the user touches the button again. So far everything works just fine. The problem is that the animation takes some time - e.g. 3 seconds. During that time I still want the user to be able to interact with the interface. So when the user touches the button again while the animation is still in progress the animation is supposed to stop right where it is and reverse.
In the Apple Q&As I have found a way to pause all animations immediately:
https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html
But I do not see a way to reverse the animation from here (and omit the rest of the initial animation). How do I accomplish this?
- (IBAction)toggleMeter:(id)sender {
if (self.myView.hidden) {
self.myView.hidden = NO;
[UIView animateWithDuration:3 animations:^{
self.myView.transform = expandMatrix;
} completion:nil];
} else {
[UIView animateWithDuration:3 animations:^{
self.myView.transform = shrinkMatrix;
} completion:^(BOOL finished) {
self.myView.hidden = YES;
}];
}
}
In addition to the below (in which we grab the current state from the presentation layer, stop the animation, reset the current state from the saved presentation layer, and initiate the new animation), there is a much easier solution.
If doing block-based animations, if you want to stop an animation and launch a new animation in iOS versions prior to 8.0, you can simply use the UIViewAnimationOptionBeginFromCurrentState option. (Effective in iOS 8, the default behavior is to not only start from the current state, but to do so in a manner that reflects both the current location as well as the current velocity, rendering it largely unnecessary to worry about this issue at all. See WWDC 2014 video Building Interruptible and Responsive Interactions for more information.)
[UIView animateWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
animations:^{
// specify the new `frame`, `transform`, etc. here
}
completion:NULL];
You can achieve this by stopping the current animation and starting the new animation from where the current one left off. You can do this with Quartz 2D:
Add QuartzCore.framework to your project if you haven't already. (In contemporary versions of Xcode, it is often unnecessary to explicitly do this as it is automatically linked to the project.)
Import the necessary header if you haven't already (again, not needed in contemporary versions of Xcode):
#import <QuartzCore/QuartzCore.h>
Have your code stop the existing animation:
[self.subview.layer removeAllAnimations];
Get a reference to the current presentation layer (i.e. the state of the view as it is precisely at this moment):
CALayer *currentLayer = self.subview.layer.presentationLayer;
Reset the transform (or frame or whatever) according to the current value in the presentationLayer:
self.subview.layer.transform = currentLayer.transform;
Now animate from that transform (or frame or whatever) to the new value:
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
Putting that all together, here is a routine that toggles my transform scale from 2.0x to identify and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
self.subview.layer.transform = currentLayer.transform;
CATransform3D newTransform;
self.large = !self.large;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
Or if you wanted to toggle frame sizes from 100x100 to 200x200 and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
CGRect newFrame = currentLayer.frame;
self.subview.frame = currentLayer.frame;
self.large = !self.large;
if (self.large)
newFrame.size = CGSizeMake(200.0, 200.0);
else
newFrame.size = CGSizeMake(100.0, 100.0);
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.frame = newFrame;
}
completion:NULL];
}
By the way, while it generally doesn't really matter for really quick animations, for slow animations like yours, you might want to set the duration of the reversing animation to be the same as how far you've progressed in your current animation (e.g., if you're 0.5 seconds into a 3.0 second animation, when you reverse, you probably don't want to take 3.0 seconds to reverse that small portion of the animation that you have done so far, but rather just 0.5 seconds). Thus, that might look like:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CFTimeInterval duration = kAnimationDuration; // default the duration to some constant
CFTimeInterval currentMediaTime = CACurrentMediaTime(); // get the current media time
static CFTimeInterval lastAnimationStart = 0.0; // media time of last animation (zero the first time)
// if we previously animated, then calculate how far along in the previous animation we were
// and we'll use that for the duration of the reversing animation; if larger than
// kAnimationDuration that means the prior animation was done, so we'll just use
// kAnimationDuration for the length of this animation
if (lastAnimationStart)
duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart));
// save our media time for future reference (i.e. future invocations of this routine)
lastAnimationStart = currentMediaTime;
// if you want the animations to stay relative the same speed if reversing an ongoing
// reversal, you can backdate the lastAnimationStart to what the lastAnimationStart
// would have been if it was a full animation; if you don't do this, if you repeatedly
// reverse a reversal that is still in progress, they'll incrementally speed up.
if (duration < kAnimationDuration)
lastAnimationStart -= (kAnimationDuration - duration);
// grab the state of the layer as it is right now
CALayer *currentLayer = self.subview.layer.presentationLayer;
// cancel any animations in progress
[self.subview.layer removeAllAnimations];
// set the transform to be as it is now, possibly in the middle of an animation
self.subview.layer.transform = currentLayer.transform;
// toggle our flag as to whether we're looking at large view or not
self.large = !self.large;
// set the transform based upon the state of the `large` boolean
CATransform3D newTransform;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
// now animate to our new setting
[UIView animateWithDuration:duration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
There is a common trick you can use to do this, but it is necessary to write a separate method to shrink (and another similar one to expand):
- (void) shrink {
[UIView animateWithDuration:0.3
animations:^{
self.myView.transform = shrinkALittleBitMatrix;
}
completion:^(BOOL finished){
if (continueShrinking && size>0) {
size=size-1;
[self shrink];
}
}];
}
So now, the trick is to break the 3 seconds animation of shrinking into 10 animations (or more than 10, of course) of 0.3 sec each in which you shrink 1/10th of the whole animation: shrinkALittleBitMatrix. After each animation is finished you call the same method only when the bool ivar continueShrinking is true and when the int ivar size is positive (the view in full size would be size=10 and the view with minimum size would be size=0). When you press the button you change the ivar continueShrinking to FALSE, and then call expand. This will stop the animation in less than 0.3 seconds.
Well, you have to fill the details but I hope it helps.
First: how to remove or cancel a animation with view?
[view.layer removeAllAnimations]
if the view have many animations, such as, one animation is move from top to bottom, other is move from left to right;
you can cancel or remove a special animation like this:
[view.layer removeAnimationForKey:#"someKey"];
// the key is you assign when you create a animation
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"someKey"];
when you do that, animation will stop, it will invoke it's delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
if flag == 1, indicate animation is completed.
if flag == 0, indicate animation is not completed, it maybe cancelled、removed.
Second: so , you can do what you want to do in this delegate method.
if you want get the view's frame when the remove code excute, you can do this:
currentFrame = view.layer.presentationlayer.frame;
Note:
when you get the current frame and remove animation , the view will also animate a period time, so currentFrame is not the last frame in the device screen.
I cann't resolve this question at now. if some day I can, I will update this question.

Cancelling UIView Animation - self.view.layer removeAllAnimations not working

I've got a UIView Animation going on that I need to cancel in my iOS app. I've tried this:
[self.view.layer removeAllAnimations];
But it didn't work. The animation continued. Here is my animation code:
[UIView animateWithDuration:1.4 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
recognizer.view.transform = CGAffineTransformTranslate(recognizer.view.transform, translation.x, translation.y);
} completion:^(BOOL finished) {
NSLog(#"completed animation, now do whatever");
}];
Does anybody have any ideas as to why it's not working?
You are adding that animation to recognizer's view, hence you will have to remove it from that same view's layer.
So instead of
[self.view.layer removeAllAnimations];
you may want to
[recognizer.view.layer removeAllAnimations];
And to keep the current status of the transformation, fetch that one from the presentation layer. The presentation layer is the one that actually reflects the changes during the animation.
recognizer.view.layer.transform = recognizer.view.layer.presentationLayer.transform;
Ok - just figured it out. Changed the component beng animated from the gesture recogniser on top of the image view to the image view itself. Now, just before the code to stop the animation, I have:
truckView.frame = [[trackView.layer presentationLayer] frame];
[truckView.layer removeAllAnimations];
So this is the way to do it. Thanks for the help that led me to this answer,
Sam

Resources