如果您将动画应用于已设置动画的视图,则新动画将替换现有动画。它不只是坚持到最后。这就是为什么您的版本不能按您想要的方式工作的原因。
即使您在新动画上设置了延迟,也会发生这种情况。现有动画将被移除,新动画将在指定延迟后开始。这会阻止 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
,因为返回imagesArray
时已完全排序sortButtonWasTapped
。我们可以通过imagesArray
在开始冒泡排序之前按 Y 原点排序(使用内置排序)来解决这个问题:
- (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;
}];
}
完成此操作后,您可以单击“排序”按钮,然后在视图在屏幕上完全排序之前单击“取消”,然后再次单击“排序”,它将恢复(通过再次执行冒泡排序)。