Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/111.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Iphone 如何做';串行';GCD动画?_Iphone_Ios_Core Animation_Grand Central Dispatch - Fatal编程技术网

Iphone 如何做';串行';GCD动画?

Iphone 如何做';串行';GCD动画?,iphone,ios,core-animation,grand-central-dispatch,Iphone,Ios,Core Animation,Grand Central Dispatch,我试图在收到远程通知时,在屏幕上显示一个自定义的ui视图,持续5秒 代码如下: //customView.alpha = 1.0 here [UIView animateWithDuration:1 animations:^{ customView.alpha = 0.3; } completion:^(BO

我试图在收到远程通知时,在屏幕上显示一个自定义的
ui视图
,持续5秒

代码如下:

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

//(dispatch_queue_t)queue was created in other parts of the code
dispatch_sync(queue, ^{
    [UIView animationWithDuration:animations:...];
});
但在某些情况下,可能会在短时间间隔内发出两个通知,其中几个
customView
可能同时进行动画制作,一个可能覆盖其他

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

假设但失败

//(dispatch_queue_t)queue was created in other parts of the code
dispatch_sync(queue, ^{
    [UIView animationWithDuration:animations:...];
});
在GCD队列中制作动画后,我得到了与我使用的原始代码相同的结果,而原始代码没有使用GCD。动画仍然相互冲突


顺便说一句,我听说涉及UI的动画或任务应该总是在主线程上运行,但在我的第二段代码中,动画似乎做得很顺利。为什么?

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

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

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

操作间依赖关系为 操作,即使这些操作位于不同的位置 操作队列。操作对象未被视为准备就绪 执行,直到其所有相关操作完成执行。 对于准备执行的操作,操作队列始终 执行相对于另一个具有最高优先级的任务 行动


如果每次运行的是同一个动画,则可以只存储动画应运行的次数(与动画的“重复计数”属性不同)

当您收到远程通知时,您将递增计数器,并调用在计数器正好为1时设置动画的方法。然后,在设置动画的方法中,您递归地在完成块中调用自己,同时每次减少计数器。它看起来像这样(使用伪代码方法名):


使用队列按顺序提交动画将不起作用,因为开始动画的方法将立即返回,并且动画将添加到动画树中,以便稍后执行。队列中的每个条目都将在很短的时间内完成

如果每个动画在同一视图上运行,则默认情况下,系统应让每个动画在开始下一个动画之前完成运行

要为UIViewAnimationOptionBeginFromCurrentState选项值引用文档,请执行以下操作:

UIViewAnimationOptionBeginFromCurrentState

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

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

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

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

(基于
NSOperation
)是现成解决方案的一个示例,但仅将其用于动画非常重要

我的
操作
子类,用于将动画弹出窗口和其他内容排队:

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()
。您还可以通过一次调用取消串行队列上的所有操作,这些操作将被优雅地取消。

animateWithDuration:。。。是异步的。这将如何解决问题?每个操作都将在动画完成之前立即完成。请在动画的完成块中设置isFinished属性。从文档中可以看到:“isFinished密钥路径让客户端知道某个操作已成功完成其任务或已取消并正在退出。”您的想法与@Ducan类似。谢谢你的代码。你认为我们应该锁定“self.numberOfTimesToRunAnimation”吗?是的。通过不将属性定义为“非原子”并且从不直接访问变量(始终使用属性),系统将为您锁定变量,这样两个线程就不会同时读/写它。非常好。我用它来控制分段控制UI。通过将
selectedIndex
放入属性,并在完成时将其设置为
NSNotFound
,我不必在动画期间禁用控件。谢谢