在循环中动画UIView

时间:2012-12-05 18:25:35

标签: objective-c ios animation bubble-sort

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

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

任何帮助都将不胜感激。

3 个答案:

答案 0 :(得分: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;
}

现在我们可以编写sort函数,使用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中的视图顺序不一定相同,因为imagesArraysortButtonWasTapped返回时完全排序。我们可以在开始冒泡排序之前通过Y {origin}(使用内置排序)对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;
    }];
}

有了这个,您可以单击排序按钮,然后在视图完全排序在屏幕上之前单击取消,然后再次单击排序,它将恢复(通过再次执行冒泡排序)。

答案 1 :(得分: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];
}

答案 2 :(得分:0)

即使你调用animateWithDuration:delay:不同的时间,它们几乎在同一时间被调用(“for”时间)。由于它们都具有相同的延迟,因此您看不到动画。

一个简单的解决方法是使用动态延迟:

 [UIView animateWithDuration:AnimationDuration
                              delay:i
                            ...