如何停止和反转UIView动画?

时间:2021-08-11 07:48:44

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.

我已经激活了一个UIView,这样当用户触摸到一个切换按钮时,它会缩小,当用户再次触摸到按钮时,它会扩展到原来的大小。到目前为止一切正常。问题是动画需要一些时间,比如3秒。在此期间,我仍然希望用户能够与接口交互。所以当用户再次触摸按钮时,动画仍然在进行中,动画应该停止在它的位置和反向。

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

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;
        }];
    }
}

3 个解决方案

#1


41  

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

如果做基于块的动画,如果您想在8.0之前停止一个动画并在iOS版本中启动一个新的动画,您可以简单地使用uiviewanimationoptionbeginfrcurrentstate选项。(在ios8中,默认行为不仅从当前状态开始,而且以既反映当前位置又反映当前速度的方式进行,因此根本无需担心这个问题。参见WWDC 2014视频构建可中断和响应交互以获取更多信息。

[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:

你可以通过停止当前的动画并从当前的动画中开始新的动画来实现这一点。你可以用Quartz 2D:

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

    将QuartzCore.framework添加到项目中,如果还没有的话。(在现代版本的Xcode中,通常不需要显式地这样做,因为它会自动链接到项目。)

  2. Import the necessary header if you haven't already (again, not needed in contemporary versions of Xcode):

    如果您还没有导入必要的头文件(在当代版本的Xcode中不需要):

    #import <QuartzCore/QuartzCore.h>
    
  3. Have your code stop the existing animation:

    让你的代码停止现有的动画:

    [self.subview.layer removeAllAnimations];
    
  4. 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;
    
  5. Reset the transform (or frame or whatever) according to the current value in the presentationLayer:

    根据presentationLayer中的当前值重置转换(或框架或其他):

    self.subview.layer.transform = currentLayer.transform;
    
  6. 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:

综上所述,这里有一个例行程序,可以从2.0x切换到标识和返回:

- (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:

或者如果你想切换帧大小从100x100到200x200,然后返回:

- (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:

顺便说一下,虽然它通常并不重要对于真正快速动画,对于你这种缓慢的动画,您可能希望将扭转动画的持续时间设置为一样多远你在你当前的动画发展(例如,如果你是0.5秒到3.0秒动画,反向时,您可能不希望花3.0秒逆转很小一部分的动画到目前为止已经作出的努力,而是仅仅0.5秒)。因此,这可能是:

- (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];
}

#2


1  

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.

所以,现在的技巧是将收缩的3秒动画分为10个动画(当然,10个以上的动画),每个动画的收缩幅度为0.3秒,在整个动画中收缩1/10。当每个动画完成后,只有当bool ivar继续为true,并且当int ivar大小为正数时,才调用相同的方法(完整大小的视图为size=10,最小大小的视图为size=0)。当你按下按钮时,你会改变ivar的连续性,然后调用扩展。这将在不到0.3秒的时间内停止动画。

Well, you have to fill the details but I hope it helps.

嗯,你必须填写细节,但我希望它能有所帮助。

#3


0  

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.

如果flag == 1,则表示动画已完成。如果flag == 0,表示动画未完成,可能取消,删除。

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.

我现在还不能解决这个问题。如果有一天我能,我会更新这个问题。

#1


41  

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

如果做基于块的动画,如果您想在8.0之前停止一个动画并在iOS版本中启动一个新的动画,您可以简单地使用uiviewanimationoptionbeginfrcurrentstate选项。(在ios8中,默认行为不仅从当前状态开始,而且以既反映当前位置又反映当前速度的方式进行,因此根本无需担心这个问题。参见WWDC 2014视频构建可中断和响应交互以获取更多信息。

[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:

你可以通过停止当前的动画并从当前的动画中开始新的动画来实现这一点。你可以用Quartz 2D:

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

    将QuartzCore.framework添加到项目中,如果还没有的话。(在现代版本的Xcode中,通常不需要显式地这样做,因为它会自动链接到项目。)

  2. Import the necessary header if you haven't already (again, not needed in contemporary versions of Xcode):

    如果您还没有导入必要的头文件(在当代版本的Xcode中不需要):

    #import <QuartzCore/QuartzCore.h>
    
  3. Have your code stop the existing animation:

    让你的代码停止现有的动画:

    [self.subview.layer removeAllAnimations];
    
  4. 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;
    
  5. Reset the transform (or frame or whatever) according to the current value in the presentationLayer:

    根据presentationLayer中的当前值重置转换(或框架或其他):

    self.subview.layer.transform = currentLayer.transform;
    
  6. 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:

综上所述,这里有一个例行程序,可以从2.0x切换到标识和返回:

- (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:

或者如果你想切换帧大小从100x100到200x200,然后返回:

- (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:

顺便说一下,虽然它通常并不重要对于真正快速动画,对于你这种缓慢的动画,您可能希望将扭转动画的持续时间设置为一样多远你在你当前的动画发展(例如,如果你是0.5秒到3.0秒动画,反向时,您可能不希望花3.0秒逆转很小一部分的动画到目前为止已经作出的努力,而是仅仅0.5秒)。因此,这可能是:

- (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];
}

#2


1  

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.

所以,现在的技巧是将收缩的3秒动画分为10个动画(当然,10个以上的动画),每个动画的收缩幅度为0.3秒,在整个动画中收缩1/10。当每个动画完成后,只有当bool ivar继续为true,并且当int ivar大小为正数时,才调用相同的方法(完整大小的视图为size=10,最小大小的视图为size=0)。当你按下按钮时,你会改变ivar的连续性,然后调用扩展。这将在不到0.3秒的时间内停止动画。

Well, you have to fill the details but I hope it helps.

嗯,你必须填写细节,但我希望它能有所帮助。

#3


0  

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.

如果flag == 1,则表示动画已完成。如果flag == 0,表示动画未完成,可能取消,删除。

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.

我现在还不能解决这个问题。如果有一天我能,我会更新这个问题。