【问题标题】:Access Method After UIImageView Animation FinishUIImageView动画完成后的访问方法
【发布时间】:2012-03-06 04:44:45
【问题描述】:

我将一组图像加载到 UIImageView 中,我正在通过一个周期对其进行动画处理。显示图像后,我希望调用 @selector 以关闭当前视图控制器。使用此代码可以很好地动画图像:

NSArray * imageArray = [[NSArray alloc] initWithObjects:
                        [UIImage imageNamed:@"HowTo1.png"], 
                        [UIImage imageNamed:@"HowTo2.png"],
                        nil];
UIImageView * instructions = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

instructions.animationImages = imageArray;
[imageArray release];
instructions.animationDuration = 16.0;
instructions.animationRepeatCount = 1;
instructions.contentMode = UIViewContentModeBottomLeft;
[instructions startAnimating];
[self.view addSubview:instructions];
[instructions release];

16 秒后,我想调用一个方法。我查看了UIView 类方法setAnimationDidStopSelector:,但我无法让它与我当前的动画实现一起工作。有什么建议吗?

谢谢。

【问题讨论】:

    标签: iphone objective-c animation uiview uiimageview


    【解决方案1】:

    您可以创建一个计时器以在动画持续时间后触发。回调可以处理您想要在动画完成后执行的逻辑。在您的回调中,请务必检查动画的状态[UIImageView isAnimating],以防您需要更多时间让动画完成。

    【讨论】:

      【解决方案2】:

      使用performSelector:withObject:afterDelay::

      [self performSelector:@selector(animationDidFinish:) withObject:nil
          afterDelay:instructions.animationDuration];
      

      或使用dispatch_after:

      dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, instructions.animationDuration * NSEC_PER_SEC);
      dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
          [self animationDidFinish];
      });
      

      【讨论】:

      • 这个方法似乎很好用,谢谢 rob!但我想知道如果由于某种原因 iPhone 的帧速率下降很多会发生什么。假设从 30 到 15。在这种情况下,当调度计时器运行结束时,动画不会播放完毕,对吗?
      • 为什么你认为帧率下降会使动画花费更长的时间?如果图像视图跟不上,可能会丢帧。
      • 这正是我在 performSelector 和延迟方面遇到的问题。阵列太慢而无法加载并浪费时间。精度丢失。我需要其他解决方案...
      • dispatch_after 不准确,不能用于动画。改用 CADisplayLink
      • CADisplayLink 非常适合同步到单个显示更新。这个问题是关于检测动画的结束,其持续时间与显示器的帧速率无关。但是,如果我今天再次尝试回答这个问题,我会尝试将 UIImageView 动画包装在带有完成块的 CATransaction 中。
      【解决方案3】:

      在 ImageView.m 中

      - (void) startAnimatingWithCompleted:(void (^)(void))completedHandler {
      if (!self.isAnimating) {
          [self startAnimating];
          dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, self.animationDuration * NSEC_PER_SEC);
          dispatch_after(popTime, dispatch_get_main_queue(),completedHandler);       
      }}
      

      【讨论】:

        【解决方案4】:

        没有办法用 UIImageView 精确地做到这一点。在大多数情况下使用延迟会起作用,但是在动画发生在屏幕上之前调用 startAnimating 之后可能会有一些延迟,因此它不精确。而不是使用 UIImageView 这个动画尝试使用 CAKeyframeAnimation ,它会在动画完成时调用委托方法。大致如下:

        NSArray * imageArray = [[NSArray alloc] initWithObjects:
                            (id)[UIImage imageNamed:@"HowTo1.png"].CGImage, 
                            (id)[UIImage imageNamed:@"HowTo2.png"].CGImage,
                            nil];
        UIImageView * instructions = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        
        //create animation
        CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
        [anim setKeyPath:@"contents"];
        [anim setValues:imageArray];
        
        [anim setRepeatCount:1];
        [anim setDuration:1];
        anim.delegate = self; // delegate animationDidStop method will be called
        
        CALayer *myLayer = instructions.layer;
        
        [myLayer addAnimation:anim forKey:nil];
        

        然后只需实现animationDidStop委托方法

        【讨论】:

        • 无法将 CGImage 添加到数组中。
        • 要摆脱将 CGImage 添加到数组中引起的警告,只需将其转换为 id。
        • 答案已编辑以反映上述情况。见apples doc
        【解决方案5】:

        使用这种方法,您可以避免在 imageView 由于图像加载缓慢而仍在动画时触发计时器。 您可以通过这种方式同时使用performSelector: withObject: afterDelay: 和 GCD:

        [self performSelector:@selector(didFinishAnimatingImageView:)
                   withObject:imageView
                   afterDelay:imageView.animationDuration];
        

        /!\self.imageView.animationDuration必须在startAnimating之前配置,否则为0

        如果-(void)didFinishAnimatingImageView:创建一个后台队列,检查isAnimating属性,而不是在主队列上执行其余部分

        - (void)didFinishAnimatingImageView:(UIImageView*)imageView
        {
        
           dispatch_queue_t backgroundQueue = dispatch_queue_create("com.yourcompany.yourapp.checkDidFinishAnimatingImageView", 0);
           dispatch_async(backgroundQueue, ^{
        
               while (self.imageView.isAnimating)
                   NSLog(@"Is animating... Waiting...");
        
               dispatch_async(dispatch_get_main_queue(), ^{
        
                  /* All the rest... */
        
               });
        
           });
        
        }
        

        【讨论】:

          【解决方案6】:

          你可以简单的使用这个分类UIImageView-AnimationCompletionBlock

          对于 Swift 版本:UIImageView-AnimationImagesCompletionBlock

          查看本课程的自述文件..

          在项目中添加 UIImageView+AnimationCompletion.hUIImageView+AnimationCompletion.m 文件,然后将 UIImageView+AnimationCompletion.h 文件导入到您所在的位置想用这个..

          例如。

          NSMutableArray *imagesArray = [[NSMutableArray alloc] init];
          for(int i = 10001 ; i<= 10010 ; i++)
              [imagesArray addObject:[UIImage imageNamed:[NSString stringWithFormat:@"%d.png",i]]];
          
          self.animatedImageView.animationImages = imagesArray;
          self.animatedImageView.animationDuration = 2.0f;
          self.animatedImageView.animationRepeatCount = 3;
          [self.animatedImageView startAnimatingWithCompletionBlock:^(BOOL success){
           NSLog(@"Animation Completed",);}];
          

          【讨论】:

          • 我不知道如何快速使用它。添加到桥接头后它仍然不起作用。
          【解决方案7】:

          从 GurPreet_Singh 答案创建 Swift 版本 (UIImageView+AnimationCompletion) 我无法为 UIImageView 创建扩展,但我相信这很容易使用。

          class AnimatedImageView: UIImageView, CAAnimationDelegate {
          
              var completion: ((_ completed: Bool) -> Void)?
          
              func startAnimate(completion: ((_ completed: Bool) -> Void)?) {
                  self.completion = completion
                  if let animationImages = animationImages {
                      let cgImages = animationImages.map({ $0.cgImage as AnyObject })
                      let animation = CAKeyframeAnimation(keyPath: "contents")
                      animation.values = cgImages
                      animation.repeatCount = Float(self.animationRepeatCount)
                      animation.duration = self.animationDuration
                      animation.delegate = self
          
                      self.layer.add(animation, forKey: nil)
                  } else {
                      self.completion?(false)
                  }
              }
          
              func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
                  completion?(flag)
              }
          }
          
          
          let imageView = AnimatedImageView(frame: CGRect(x: 50, y: 50, width: 200, height: 135))
          imageView.image = images.first
          imageView.animationImages = images
          imageView.animationDuration = 2
          imageView.animationRepeatCount = 1
          view.addSubview(imageView)
          
          imageView.startAnimate { (completed) in
               print("animation is done \(completed)")
               imageView.image = images.last
          }
          

          【讨论】:

          • 像魅力一样工作我唯一添加的是animation.calculationMode = .discrete 以禁用帧之间的插值。它使它看起来像一个低 fps 的 gif。
          猜你喜欢
          • 1970-01-01
          • 2023-03-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多