0

バブルソートがどのように機能するかを示すアプリを作成しています。私が抱えている問題の1つは、アニメーションです。スキップしているようで、アニメーションが一斉に発射されます。if ステートメント内で、2 つの画像を交換し、交換をアニメーション化します。次に、遅延が必要なため、交換するとループに戻り、次の 2 つの画像が交換されます。以下は私のコードです。sleep() のような多くのことを試しましたが、うまくいかなかったようです。画像は 4 つのボックスで、左から右、小さいものから大きいものへと入れ替えています。

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

任意のヘルプをいただければ幸いです。

4

3 に答える 3

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

したがって、これは問題のない解決策ですが、1 つの欠点があります。dispatch_after保留中のブロックをキューから削除する API がないため、アニメーションをキャンセルするボタンを追加するのは簡単ではありません。

キャンセルをサポートしたい場合は、保留中の操作のキューを明示的に管理することをお勧めします。キューを保持するインスタンス変数を追加することで、これを行うことができます。

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

したがって、キューが空の場合は停止します。それ以外の場合は、キューから最初のブロックを取り出し、それをアニメーションのアニメーション ブロックとして使用しますUIViewrunPendingSwapsアニメーションに、現在のアニメーションが終了した後に次の保留中のアニメーション (ある場合) を開始する完了ブロックを再度呼び出します。

pendingSwapsキャンセルするには、 nilに設定するだけです。

- (IBAction)cancelButtonWasTapped {
    pendingSwaps = nil;
}

ユーザーが [キャンセル] をタップした場合、が戻るときに完全に並べ替えられているimagesArrayため、ビューは必ずしも画面上の順序と同じになるとは限らないことに注意してください。バブル ソートを開始する前に、(組み込みのソートを使用して) Y 原点でソートすることで、これを修正できます。imagesArraysortButtonWasTappedimagesArray

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

この状態で、[並べ替え] ボタンをクリックし、ビューが画面上で完全に並べ替えられる前に [キャンセル] をクリックし、もう一度 [並べ替え] をクリックすると、(バブル 並べ替えを再度実行して) 再開されます。

于 2012-12-06T05:32:36.990 に答える
0

animateWithDuration:delay:を異なる時間に呼び出したとしても、それらはほぼ同時に(「for」時間)呼び出されます。それらすべてに同じ遅延があるため、アニメーションは表示されません。

簡単な修正は、動的遅延を使用することです。

 [UIView animateWithDuration:AnimationDuration
                              delay:i
                            ...
于 2012-12-05T20:33:25.593 に答える
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!");
    }
}

これは単に、最初の 2 つのビューの順序が正しくないものを見つけて交換し、それらを互いの中心点に同時に移動します。もちろん、順不同の各ペアに対して実行するアニメーションの種類を選択できます。

完全を期すために、最初にタイマーとビューを設定する方法を次に示します。

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];
}
于 2012-12-05T19:14:13.970 に答える