【问题标题】:How to use GCD to control animation sequence如何使用 GCD 控制动画序列
【发布时间】:2014-12-22 11:25:18
【问题描述】:

我有一个View 显示和隐藏给用户一些提示。

显示和隐藏方法如下所示:

-(void)show{
    [UIView animateWithDuration:3.0f
                 animations:^{
                     //do something to show self to give hint;
                     self.frame = CGRectMake(0,0,0,0);
                 } completion:nil];
}

-(void)hide{
    [UIView animateWithDuration:3.0f
                 animations:^{
                     //do something to hide self to give hint;
                     self.frame = CGRectMake(centerX,centerY,100,100);
                 } completion:nil];
}

显示新视图时,我必须调用hide method,然后调用show method。但是持续时间延迟 3.0f 会导致一些错误。我正在使用这样的方法:

dispatch_async(dispatch_get_main_queue(), ^{
    [view hide];
});

dispatch_async(dispatch_get_main_queue(), ^{
    [view show];
});

我在hide method 之后打电话给show method。动画不能按照它们提交到队列的顺序执行。我想要的是在hide method 完成之后执行的show method。如何控制这两种方法的顺序。

我认为我无法使用完成处理程序,因为我无法确定调用这两个方法的位置,或者当我调用另一个 show methodhide method 时是否显示视图。

如果我不清楚,有什么建议吗?我将重新编辑我的问题。


PS:

这不仅仅是一个闪光。当调用下一个 show 方法时,我无法保证显示或隐藏最后一个视图以及显示最后一个视图的时间,也就是说,如果正在显示视图并且已经调用了 hide 方法并且已经完成,然后调用show方法,结果是对的。如果正在显示视图,则需要显示另一个提示视图,我会先调用hide,然后调用show,因为main_queue 是串行的但动画块是同步执行的,所以结果是错误的。我正在寻找 GCD 中是否有某种锁可以帮助我在最后一个排队块完成后执行一个块,而不是在 showhide 方法内更改。因为有许多其他对 showhide 方法的调用具有许多不同类型的参数,我需要修复我的代码中的许多地方。

【问题讨论】:

  • An answer to this question 建议使用关键帧动画,这可能对您有用。
  • 将标志传递给 show 或 hide 方法,指示您是否要调用下一个。然后在完成方法中对其进行操作。
  • @jrturton 的解决方案将起作用。您还可以创建一个NSOperationQueue 并将您的显示/隐藏命令添加到此队列。如果操作不再运行,您可以调用 cancel
  • @AaronBrager 我试过 NSOperationQueue。似乎即使我在将新操作添加到队列之前取消了所有操作,添加到队列的动画也无法取消。

标签: ios animation grand-central-dispatch


【解决方案1】:

如果您想按照添加到队列中的顺序一次执行一项任务,请使用串行队列。

因此您可以使用串行队列按添加的顺序一次执行显示和隐藏任务。是的,主队列没问题。

然而 UIView -animateWithDuration:animations: 方法是一种异步调用,该方法立即返回。所以你需要等到完成块被调用。

如果您想等到某些任务完成,请使用调度组。但是你应该避免在主队列上等待。它阻塞了主队列。糟糕的应用程序。

因此,您可能需要使用串行队列和调度组,如下所示。

属性和初始化

@property (nonatomic, strong) dispatch_queue_t serialQueue;
@property (nonatomic, strong) dispatch_group_t group;

-(void)initQueue {
    // create a serial queue
    self.serialQueue = dispatch_queue_create("com.example.serialQueue", 0);
    // create a dispatch group
    self.group = dispatch_group_create();
}

使用串行队列和调度组的方法

-(void)animateSyncWithDuration:(NSTimeInterval)duration animations:(block_t)animations {
    dispatch_async(self.serialQueue, ^{
        /*
         * This block is invoked on the serial queue
         * This block would never be executed concurrently
         */

        /*
         * Enter the dispatch group
         */
        dispatch_group_enter(self.group);

        dispatch_async(dispatch_get_main_queue(), ^{
            /*
             * This block is invoked on the main queue
             * It is safe to use UIKit
             */
            [UIView animateWithDuration:duration animations:animations completion:^{
                /*
                 * This completion block is invoked on the main queue
                 * Now leave the dispatch group
                 */
                dispatch_group_leave(self.group);
            }];
        });

        /*
         * Wait until leaving the dispatch group from the UIView animation completion block
         * It means it blocks the serial queue
         */
        dispatch_group_wait(self.group, DISPATCH_TIME_FOREVER);
    });
}

显示和隐藏

-(void)show{
    [self animateSyncWithDuration:3.0f animations:^{
        //do something to show self to give hint;
        self.frame = CGRectMake(0,0,0,0);
    }];
}

-(void)hide{
    [self animateSyncWithDuration:3.0f animations:^{
        //do something to hide self to give hint;
        self.frame = CGRectMake(centerX,centerY,100,100);
    }];
}

【讨论】:

    【解决方案2】:

    如果您想要的是一个动作(隐藏然后显示自己),您应该只制作一个动画来执行此操作,而不是合并两个动画。

    有两种可能的解决方案。

    (1) 使用动画重复和自动反转(需要在完成回调中重置回原始大小)

    -(void) flash {
      CGRect bounds = self.bounds;
    
      [UIView animateWithDuration:1.0f
                            delay:0.0f
                          options:UIViewAnimationOptionAutoreverse |
                                  UIViewAnimationOptionRepeat
                       animations:^{
                         [UIView setAnimationRepeatCount:1];
                         self.bounds = CGRectZero;
                       }
                       completion:^(BOOL finished) {
                         self.bounds = bounds;
                       }];
    }
    

    (2)使用关键帧动画

    -(void) flash2 {
        [UIView animateKeyframesWithDuration:1.0f
            delay:0.0f
          options:UIViewKeyframeAnimationOptionCalculationModeLinear
       animations:^{
           CGRect bounds = self.bounds;
    
           [UIView addKeyframeWithRelativeStartTime:0.0
                                   relativeDuration:0.5
                                         animations:^{ self.bounds = CGRectZero; }];
    
           [UIView addKeyframeWithRelativeStartTime:0.5
                                   relativeDuration:0.5
                                         animations:^{ self.bounds = bounds; }];
        }
       completion:nil];
    }
    

    【讨论】:

      【解决方案3】:

      我正在使用以下方式将delay 放入function calling

      - (void) doAnimation : (double) delay {
      
          dispatch_async(dispatch_get_main_queue(), ^{
              NSLog(@"Call your First function here");
          });
      
          double delayInSeconds = delay;
          dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
          dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
      
              NSLog(@"Call your second function here");
      
          });
      } 
      

      【讨论】:

      • 延迟秒数可能会改变,有什么不使用延迟秒数的建议吗?
      • 创建以delay seconds为参数的函数。或者你可以在Animation 中使用completion block。见updated answer
      【解决方案4】:

      我可能没有完全理解用例,但我认为你应该在这里做的是检查隐藏操作是否真的需要发生。此外,由于代码中隐藏的动画持续时间为 3 秒,因此您应该使用完成块创建您的方法,这样您就可以执行类似于我在下面用伪代码编写的内容:

      - (void)hideIfNeededWithCompletionBlock:((^)())completionBlock {
          if (self.isShowing) {
              [self hideWithCompletionBlock:^(BOOL didHide) {
                  if (completionBlock) {
                      completionBlock();
                  }
              }];
          } else {
              if (completionBlock) {
                  //We didn't need to hide anything, so we're done
                  completionBlock();
              }
          }
      }
      

      那么你可以这样称呼它:

      [self hideIfNeededWithCompletionBlock:^(){
          [self show];
      }];
      

      如果您需要这种灵活性,您可以使用 show 方法执行类似的操作。

      此外,根据您的需要,您可以让您的方法采用 BOOL 来确定是否为显示/隐藏设置动画,如果您通过 NO,则使用 0.0 的持续时间。

      我认为当您可以使用提供的 API 处理所有内容时,它会针对 UIView 动画 API 开始将其包装在 dispatch_async 块中。

      【讨论】:

      • 我知道。即使我不使用GCD,只是直接将show和hide一起使用,结果也是错误的,除了我在完成块内调用show或hide。
      • 我正在寻找更好的解决方案,比如某种锁,以确保如果 show 方法没有完成,则 hide 方法将等到 show 方法完成。
      • 因此可以使用 BOOL 来跟踪是否存在待处理的隐藏操作,并使用 BOOL 来跟踪是否正在发生显示动画。如果您在显示动画中,请将您的 pendingHide BOOL 设置为 YES。显示后,如果有未决隐藏,则执行它(从动画完成块调用)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-23
      • 1970-01-01
      相关资源
      最近更新 更多