10

当远程通知到来时,我正在尝试UIView在屏幕上自定义显示 5 秒。

像这样的代码:

//customView.alpha = 1.0 here
[UIView animateWithDuration:1 animations:^{
                                  customView.alpha = 0.3;
                              } 
                              completion:^(BOOL finished){
                                  // remove customView from super view.
                              }];

问题和我需要什么

但是在某些情况下,一些通知可能会在很短的时间间隔内出现,其中几个customView可能同时动画,一个可能覆盖其他。

我希望这些动画一个接一个地执行,这样它们就不会发生冲突。

假设但失败

//(dispatch_queue_t)queue was created in other parts of the code
dispatch_sync(queue, ^{
    [UIView animationWithDuration:animations:...];
});

在 GCD 队列中制作动画后,我得到了与我使用的原始代码相同的结果,它没有使用 GCD。动画仍然存在冲突。

顺便说一句,我听说涉及 UI 的动画或任务应该始终在主线程上运行,但在我的第二个代码中,动画看起来很流畅。为什么?

4

5 回答 5

4

如果每次都运行相同的动画,那么您可以只存储动画应该运行的次数(与动画的重复计数属性不同)。

当您收到远程通知时,您增加计数器并调用动画方法,如果计数器正好是一。然后在 methodThatAnimates 中,您在完成块中递归调用自己,同时每次减少计数器。它看起来像这样(带有伪代码方法名称):

- (void)methodThatIsRunWhenTheNotificationIsReceived {
    // Do other stuff here I assume...
    self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation + 1;
    if ([self.numberOfTimesToRunAnimation == 1]) {
        [self methodThatAnimates];
    }
}

- (void)methodThatAnimates {
    if (self.numberOfTimesToRunAnimation > 0) {
        // Animation preparations ...
        [UIView animateWithDuration:1 
                         animations:^{
                                  customView.alpha = 0.3;
                         } 
                         completion:^(BOOL finished){
                                  // Animation clean up ...
                                  self.numberOfTimesToRunAnimation = self.numberOfTimesToRunAnimation - 1;
                                  [self methodThatAnimates];
                         }];
    }
}
于 2012-05-30T14:48:54.297 回答
4

使用队列顺序提交动画是行不通的,因为开始动画的方法会立即返回,然后将动画添加到动画树中,稍后再执行。队列中的每个条目将在几分之一秒内完成。

如果您的每个动画都在同一个视图上运行,那么默认情况下,系统应该让每个动画在开始下一个动画之前完成运行。

引用 UIViewAnimationOptionBeginFromCurrentState 选项值的文档:

UIViewAnimationOptionBeginFromCurrentState

从与已经进行中的动画关联的当前设置开始动画。 如果此键不存在,则允许任何飞行中的动画在新动画开始之前完成。如果另一个动画不在飞行中,则此键无效。

如果你想链接一系列动画,我会这样做:

创建一个可变的动画块数组。(代码块是对象,可以添加到数组中。)编写一个方法,将顶部动画块拉出数组(并将其从数组中删除)并使用 animateWithDuration:animations:completion 提交它,其中完成方法简单再次调用该方法。使代码在从数组中拉出一个项目之前断言一个锁,并在删除该项目后释放锁。

然后,您可以编写响应传入通知的代码,方法是断言您的动画数组锁、将动画块添加到锁并释放锁。

于 2012-05-30T20:58:54.077 回答
1

您可以使用(非)并发NSOperationQueue来“逐步”执行动画

NSOperationQueue 类管理一组 NSOperation 对象的执行。被添加到队列后,操作将保留在该队列中,直到它被显式取消或完成其任务的执行。队列中的操作(但尚未执行)本身根据优先级和互操作对象依赖性进行组织,并相应地执行。一个应用程序可以创建多个操作队列并向其中的任何一个提交操作。

操作间的依赖关系为操作提供了绝对的执行顺序,即使这些操作位于不同的操作队列中。直到其所有相关操作都完成执行后,才认为操作对象已准备好执行。对于准备好执行的操作,操作队列总是执行相对于其他准备好的操作具有最高优先级的操作。

于 2012-05-30T13:56:34.753 回答
0

我建议在完成块中向触发动画的任何对象发送一条消息。然后,您可以让该对象将通知本身排队,并在每次收到消息时启动下一个。

于 2012-05-30T13:55:06.777 回答
0

ProcedureKit(基于NSOperation)是现成解决方案的一个示例,但仅将其用于动画是相当重量级的。

Operation用来排队动画弹出窗口和其他东西的子类:

class SerialAsyncOperation: Operation {

    private var _started = false

    private var _finished = false {
        willSet {
            guard _started, newValue != _finished else {
                return
            }
            willChangeValue(forKey: "isFinished")
        }
        didSet {
            guard _started, oldValue != _finished else {
                return
            }
            didChangeValue(forKey: "isFinished")
        }
    }

    private var _executing = false {
        willSet {
            guard newValue != _executing else {
                return
            }
            willChangeValue(forKey: "isExecuting")
        }
        didSet {
            guard oldValue != _executing else {
                return
            }
            didChangeValue(forKey: "isExecuting")
        }
    }

    override var isAsynchronous: Bool {
        return true
    }

    override var isFinished: Bool {
        return _finished
    }

    override var isExecuting: Bool {
        return _executing
    }

    override func start() {
        guard !isCancelled else {
            return
        }
        _executing = true
        _started = true
        main()
    }

    func finish() {
        _executing = false
        _finished = true
    }

    override func cancel() {
        _executing = false
        _finished = true
        super.cancel()
    }
}

使用示例:

// Setup a serial queue
private lazy var serialQueue: OperationQueue = {
    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 1
    queue.name = String(describing: type(of: self))
    return queue
}()

// subclass SerialAsyncOperation
private class MessageOperation: SerialAsyncOperation {

    // ...

    override func main() {
        DispatchQueue.main.async { [weak self] in
            // do UI stuff

            self?.present(completion: {
                self?.finish()  
            })
        }
    }

    func present(completion: @escaping () -> Void) {
        // do async animated presentation, calling completion() in its completion
    }

    func dismiss(completion: @escaping () -> Void) {
        // do async animated dismissal, calling completion() in its completion
    }

    // animated cancellation support
    override func cancel() {
        if isExecuting {
            dismiss(completion: {
                super.cancel()
            })
        } else {
            super.cancel()
        }
    }
}

finish()基本上,只需将此操作添加到串行队列中,并记住在完成异步操作时调用。您还可以通过一次调用取消串行队列上的所有操作,这些操作将被优雅地解除。

于 2017-08-26T22:49:10.980 回答