UIViewAnimationOptionBeginFromCurrentState具有基本动画的意外行为

时间:2014-02-01 08:42:09

标签: ios objective-c cocoa-touch uiviewanimation

我正在尝试在收到按钮点击时执行此基本UIView动画:

- (IBAction)buttonPress:(id)sender
{
    self.sampleView.alpha = 0.0;
    [UIView animateWithDuration:2.0
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState
                     animations:^{self.sampleView.alpha = 1.0;}
                     completion:NULL];
}

在按钮之前,单击视图是可见的,其alpha值为1.0。当我发送按钮动作时,我希望视图在2秒内从alpha = 0.0淡入alpha = 1.0,但这不会发生。当我删除UIViewAnimationOptionBeginFromCurrentState动画工作正常。

似乎设置了UIViewAnimationOptionBeginFromCurrentState选项,没有设置alpha = 0.0并且跳过动画,因为它认为它已经是1.0。

我试图理解为什么会发生这种情况,因为Apple文档指出如果另一个动画没有进行,UIViewAnimationOptionBeginFromCurrentState没有效果:

UIViewAnimationOptionBeginFromCurrentState 从与已在飞行中的动画相关联的当前设置开始动画。如果此键不存在,则允许在新动画开始之前完成任何正在进行的动画。如果另一个动画没有在飞行中,则此键无效。“

3 个答案:

答案 0 :(得分:3)

事实证明,使用UIViewAnimationOptionBeginFromCurrentState并不总是按预期工作。

看看这个例子:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    UIButton *theButton = [UIButton new];
    [self.view addSubview:theButton];
    theButton.frame = self.view.frame;
    theButton.backgroundColor = [UIColor redColor];
    theButton.alpha = 0.05;
    [theButton addTarget:self action:@selector(actionPressed:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)actionPressed:(UIButton *)theButton
{
    theButton.alpha = 0.6;
    [UIView animateWithDuration:5
                          delay:0
                        options:UIViewAnimationOptionBeginFromCurrentState
                     animations:^
     {
         // you expect to animate from 0.6 to 1. but it animates from 0.05 to 1
         theButton.alpha = 1;
     }
                     completion:nil];
}

在上面的示例中,您希望.alpha的动画效果为0.6到1.但是,动画从0.05到1。

要解决此问题,您应将actionPressed:更改为以下内容:

- (void)actionPressed:(UIButton *)theButton
{
    [UIView animateWithDuration:0
                     animations:^
     {
         // set new values INSIDE of this block, so that changes are
         // captured in UIViewAnimationOptionBeginFromCurrentState.
         theButton.alpha = 0.6;
     }
                     completion:^(BOOL finished)
     {
         [UIView animateWithDuration:5
                               delay:0
                             options:UIViewAnimationOptionBeginFromCurrentState
                          animations:^
          {
              // now it really animates from 0.6 to 1
              theButton.alpha = 1;
          }
                          completion:nil];
     }];
}

提及animateWithDuration:0 !!!

规则很简单:只使用带有UIViewAnimationOptionBeginFromCurrentState动画块的其他动画块,以便实际应用之前的所有更改。

如果您不知道animateWithDuration:0块中应该包含哪些内容,那么您可以使用此技巧:

- (void)actionPressed:(UIButton *)theButton
{
    // make all your changes outside of the animation block
    theButton.alpha = 0.6; 

    // create a fake view and add some animation to it.
    UIView *theFakeView = [UIView new];
    theFakeView.alpha = 1;
    [UIView animateWithDuration:0
                     animations:^
     {
         // we need this line so that all previous changes are ACTUALLY applied
         theFakeView.alpha = 0;
     }
                     completion:^(BOOL finished)
     {
         [UIView animateWithDuration:5
                               delay:0
                             options:UIViewAnimationOptionBeginFromCurrentState
                          animations:^
          {
              // now it really animates from 0.6 to 1
              theButton.alpha = 1;
          }
                          completion:nil];
     }];
}

如果您不想记住此错误的所有详细信息,请将Method Swizzling应用于UIView类。

重要编辑:如果调用performBatchUpdates:completion:实例UICollectionView,则以下代码会导致运行时崩溃。因此,我不建议在这种情况下使用method swizzling

您的代码可能如下所示:

+ (void)load
{
    static dispatch_once_t theOnceToken;
    dispatch_once(&theOnceToken, ^
                  {
                      Class theClass = object_getClass(self);
                      SEL theOriginalSelector = @selector(animateWithDuration:delay:options:animations:completion:);
                      SEL theSwizzledSelector = @selector(swizzled_animateWithDuration:delay:options:animations:completion:);
                      Method theOriginalMethod = class_getClassMethod(theClass, theOriginalSelector);
                      Method theSwizzledMethod = class_getClassMethod(theClass, theSwizzledSelector);

                      if (!theClass ||!theOriginalSelector || !theSwizzledSelector || !theOriginalMethod || !theSwizzledMethod)
                      {
                          abort();
                      }

                      BOOL didAddMethod = class_addMethod(theClass,
                                                          theOriginalSelector,
                                                          method_getImplementation(theSwizzledMethod),
                                                          method_getTypeEncoding(theSwizzledMethod));

                      if (didAddMethod)
                      {
                          class_replaceMethod(theClass,
                                              theSwizzledSelector,
                                              method_getImplementation(theOriginalMethod),
                                              method_getTypeEncoding(theOriginalMethod));
                      }
                      else
                      {
                          method_exchangeImplementations(theOriginalMethod, theSwizzledMethod);
                      }
                  });
}

+ (void)swizzled_animateWithDuration:(NSTimeInterval)duration
                               delay:(NSTimeInterval)delay
                             options:(UIViewAnimationOptions)options
                          animations:(void (^)(void))animations
                          completion:(void (^)(BOOL))completion
{
    if (options & UIViewAnimationOptionBeginFromCurrentState)
    {
        UIView *theView = [UIView new];
        theView.alpha = 1;
        [UIView animateWithDuration:0
                         animations:^
         {
             theView.alpha = 0;
         }
                         completion:^(BOOL finished)
         {
             [self swizzled_animateWithDuration:duration
                                          delay:delay
                                        options:options
                                     animations:animations
                                     completion:completion];
         }];
    }
    else
    {
        [self swizzled_animateWithDuration:duration
                                     delay:delay
                                   options:options
                                animations:animations
                                completion:completion];
    }
}

如果您将此代码添加到自定义UIView类别,那么此代码现在可以正常工作:

- (void)actionPressed:(UIButton *)theButton
{
    theButton.alpha = 0.6;
    [UIView animateWithDuration:5
                          delay:0
                        options:UIViewAnimationOptionBeginFromCurrentState
                     animations:^
     {
         // you expect to animate from 0.6 to 1.
         // it will do so ONLY if you add the above code sample to your project.
         theButton.alpha = 1;
     }
                     completion:nil];
}

在我的情况下,这个错误完全破坏了我的动画。见下文:

[Demo CountPages alpha] [Demo CountPages alpha]

答案 1 :(得分:1)

为了查看正在发生的事情,动画帧位置而不是alpha是有帮助的。如果您这样做,请尝试每隔一秒左右点按一下按钮,并设置UIViewAnimationOptionBeginFromCurrentState,您就会看到一些动作。

当您将alpha设置为零时,在下一次运行循环之前,这不会更新显示。由于你之后立即开始动画,显示器没有机会更新,当动画框架开始时,它会查看当前显示的alpha(1.0)并查看它必须到达的位置(alpha为1.0)和两者之间的插值没有任何作用。

如果您需要保留UIViewAnimationOptionBeginFromCurrentState选项,因为当此视图上的动画正在进行时可能会点按此按钮,您可以执行以下操作:

- (IBAction)buttonPressed:(id)sender
{
  self.sampleView.alpha = 0.0;

  [self performSelector:@selector(animateAlpha) withObject:nil afterDelay:0.1];
}

- (void)animateAlpha
{
  [UIView animateWithDuration:2.0
                        delay:0.0
                      options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState
                   animations:^{self.sampleView.alpha = 1.0;}
                   completion:NULL];
}

答案 2 :(得分:0)

使用UIViewAnimationOptionBeginFromCurrentState代替performSelector:时,我想在调用UIView之前使用animateWithDuration:&#39; s performWithoutAnimation设置初始值。< / p>