2012-12-05 45 views
0

我正在创建一个应用程序,它将显示气泡排序的工作原理。我遇到的一个问题是动画。它似乎在跳过,动画一下子全部点亮。在我的if语句中,我想交换两个图像并为交换设置动画。然后我想要一个延迟,所以一旦它们交换,它将通过循环返回并交换下两个图像。以下是我的代码。我尝试了很多东西,比如sleep(),但它似乎没有工作。图像是四个框,我将它们从左到右,从最小到最大交换。在循环中动画UIView

for (int i = 0; i < 3; i++) 
{ 
    UIImageView *temp1 = [imagesArray objectAtIndex:i]; 
    UIImageView *temp2 = [imagesArray objectAtIndex:i+1]; 
    temp1Width = temp1.frame.size.width; 
    temp2Width = temp2.frame.size.width; 
    if (temp1Width > temp2Width) 
    { 
     CGPoint tempPoint = temp1.center; 

     [UIView animateWithDuration:1.0 
           delay: 1.0 
          options: nil 
         animations:^{ 
          temp1.center = temp2.center; 

         } 
         completion:^(BOOL finished){ 
          [UIView animateWithDuration:1.0 
                delay: 0.0 
               options: nil 
               animations:^{ 
                temp2.center = tempPoint; 
               } 
               completion:^(BOOL finished){ 
               }]; 
         }]; 
     [imagesArray exchangeObjectAtIndex:i+1 withObjectAtIndex:i]; 
    } 
} 

任何帮助,将不胜感激。

回答

2

如果应用动画为已被动画视图,新的动画替换现有的动画。它不只是被固定在最后。这就是为什么你的版本不能按你想要的方式工作。

即使您在新动画上设置了延迟,也会发生这种情况。现有的动画将被删除,并且新的动画将在指定的延迟后开始。这可以防止k20从工作中获得简单的答案。 (太糟糕了,因为这将是一个非常简单的解决方案。)

所以,一个非常简单的选择是推迟添加每个额外的动画。使用dispatch_after函数很容易。首先,让我们分解出的是交换意见Y起源到一个辅助函数的代码:

static void swapViewYOrigins(UIView *a, UIView *b) { 
    CGRect aFrame = a.frame; 
    CGRect bFrame = b.frame; 
    CGFloat t = aFrame.origin.y; 
    aFrame.origin.y = bFrame.origin.y; 
    bFrame.origin.y = t; 
    a.frame = aFrame; 
    b.frame = bFrame; 
} 

现在我们可以写排序功能,使得它使用dispatch_after推迟每个连续的动画:

- (IBAction)sortButtonWasTapped { 
    NSTimeInterval duration = 1; 
    dispatch_time_t time = DISPATCH_TIME_NOW; 
    for (int i = imagesArray.count - 1; i > 0; --i) { 
     for (int j = 0; j < i; ++j) { 
      UIView *a = imagesArray[j]; 
      UIView *b = imagesArray[j+1]; 
      if (a.frame.size.width > b.frame.size.width) { 
       imagesArray[j] = b; 
       imagesArray[j+1] = a; 
       dispatch_after(time, dispatch_get_main_queue(), ^{ 
        [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ 
         swapViewYOrigins(a, b); 
        } completion:nil]; 
       }); 
       time = dispatch_time(time, duration * NSEC_PER_SEC); 
      } 
     } 
    } 
} 

所以这是一个好的解决方案,但它确实有一个缺点。添加一个取消动画的按钮并不容易,因为没有API从队列中删除待处理的dispatch_after块。

如果您要支持取消,最好明确地管理未决操作的队列。我们可以做的是通过添加一个实例变量来保存队列:

@implementation ViewController { 
    IBOutletCollection(UIView) NSMutableArray *imagesArray; 
    NSMutableArray *pendingSwaps; 
} 

然后,我们修改sortButtonWasTapped到块添加到队列中,而不是调用dispatch_after,我们让它开始运行队列它完成排序后:

- (IBAction)sortButtonWasTapped { 
    pendingSwaps = [NSMutableArray array]; 
    for (int i = imagesArray.count - 1; i > 0; --i) { 
     for (int j = 0; j < i; ++j) { 
      UIView *a = imagesArray[j]; 
      UIView *b = imagesArray[j+1]; 
      if (a.frame.size.width > b.frame.size.width) { 
       imagesArray[j] = b; 
       imagesArray[j+1] = a; 
       [pendingSwaps addObject:^{ 
        swapViewYOrigins(a, b); 
       }]; 
      } 
     } 
    } 

    [self runPendingSwaps]; 
} 

我们使用和以前一样的swapViewYOrigins函数。我们运行这样的挂单交易:

- (void)runPendingSwaps { 
    if (pendingSwaps.count == 0) 
     return; 

    void (^swapBlock)(void) = pendingSwaps[0]; 
    [pendingSwaps removeObjectAtIndex:0]; 
    [UIView animateWithDuration:1 animations:swapBlock completion:^(BOOL finished) { 
     [self runPendingSwaps]; 
    }]; 
} 

所以,如果队列为空,我们就停下来。否则,我们将第一个块从队列中取出,并将其用作UIView动画的动画块。我们给动画一个完成块,再次调用runPendingSwaps,在当前动画完成后开始下一个待处理动画(如果有的话)。

要取消,我们可以只设置pendingSwaps为nil:

- (IBAction)cancelButtonWasTapped { 
    pendingSwaps = nil; 
} 

需要注意的是,如果用户水龙头取消,意见不一定会在屏幕上相同的顺序,因为它们是在imagesArray,因为当sortButtonWasTapped返回时,imagesArray已完全排序。我们可以解决这个问题通过启动冒泡排序前由Y起源排序imagesArray(使用内置排序):

- (IBAction)sortButtonWasTapped { 
    [self sortImagesArrayByY]; 
    // etc. same as previous version 
} 

- (void)sortImagesArrayByY { 
    [imagesArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { 
     CGFloat d = [obj1 frame].origin.y - [obj2 frame].origin.y; 
     return 
     d < 0 ? NSOrderedAscending 
     : d > 0 ? NSOrderedDescending 
     : NSOrderedSame; 
    }]; 
} 

有了这个地方,你可以点击排序按钮,然后单击取消之前的看法有在屏幕上完全排序,然后再次点击排序,它会恢复(再次执行冒泡排序)。

+0

感谢罗布帮助它结束了完美的工作。 – user1830229

+0

难道不可以使用完成块吗? – k20

+0

我的代码确实使用完成块。 –

0

尝试使用计时器隐式执行,而不是使用循环和延迟。例如,你可以使用计划NSTimer,其持续时间比动画时间等于或大于触发此方法:

- (void)performNextAnimation:(NSTimer *)timer 
{ 
    BOOL didSwap = NO; 

    for(NSInteger i = 0; i < [self.imageViews count] - 1; ++i) 
    { 
     UIImageView *image1 = [self.imageViews objectAtIndex:i]; 
     UIImageView *image2 = [self.imageViews objectAtIndex:(i + 1)]; 

     if(image1.frame.size.width <= image2.frame.size.width) 
      continue; 

     CGPoint center1 = image1.center; 
     CGPoint center2 = image2.center; 

     [UIView animateWithDuration:AnimationDuration 
           delay:0 
          options:UIViewAnimationOptionCurveEaseInOut 
         animations:^ 
     { 
      image1.center = center2; 
      image2.center = center1; 
     } 
         completion:nil]; 

     [self.imageViews exchangeObjectAtIndex:(i + 1) withObjectAtIndex:i]; 

     didSwap = YES; 
     break; 
    } 

    if(!didSwap) 
    { 
     [self.animationTimer invalidate]; 
     self.animationTimer = nil; 
     NSLog(@"Done!"); 
    } 
} 

这可以简单地认为是乱序的前两个看法并交换它们,移动它们同时对彼此的中心点。当然,您可以选择任何类型的动画来执行每个无序对。

为了完整起见,这里是我如何设置我的计时器和看法摆在首位:

self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:AnimationDuration 
                 target:self 
                selector:@selector(performNextAnimation:) 
                userInfo:nil 
                 repeats:YES]; 

CGFloat viewCenterX = self.view.center.x; 
self.imageViews = [NSMutableArray array]; 

const CGFloat imageHeight = 35; 
const CGFloat imageMargin = 10; 

for(size_t i = 0; i < 10; ++i) 
{ 
    CGFloat imageWidth = arc4random_uniform(250) + 30; 
    CGRect imageFrame = CGRectMake(viewCenterX - imageWidth * 0.5, i * (imageHeight + imageMargin) + imageMargin, 
            imageWidth, imageHeight); 
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame]; 

    imageView.backgroundColor = [UIColor redColor]; 
    [self.view addSubview:imageView]; 
    [self.imageViews addObject:imageView]; 
} 
0

即使你拨打animateWithDuration:延迟:不同的时代,他们被称为几乎在同一时间( “for”时间)。由于它们都具有相同的延迟,所以您看不到动画。

一个简单的办法是使用动态延迟:

[UIView animateWithDuration:AnimationDuration 
           delay:i 
          ...