虽然循环延迟不按预期工作

时间:2017-05-28 04:00:19

标签: ios swift xcode

我想创建一个启动倒计时的按钮,但在倒计时期间,按钮'图像'与传递的秒数相关的变化(例如3 - 新按钮,2 - 新按钮,1 - 新按钮)。我相信到目前为止我所获得的是实现这一目标的最有效方式。

然而,在运行时,没有图像更改,也没有在循环的下一次迭代之前等待。代码在Swift 3.0中,如下所示(省略所有初始化等):

var countDown = ["buttonThree", "buttonTwo", "buttonOne", "buttonRelease", "buttonMain"]

//The Button in its initial state
@IBAction func playBtn(_ sender: UIButton) {
    var imageCounter:Int = 0
    //Begin Loop
    while imageCounter != 3 {
        let playBtn = UIButton(type: .custom)
        playBtn.setImage(UIImage(named: countDown[imageCounter]), for: .normal)
        imageCounter = imageCounter + 1
        _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (timer) in }
        print("Working")
    }
    //Delay on third iteration
    if imageCounter == 3 {
        //Execute recording
        _ = DispatchTime.now() + 5
    }

    //Return to normal state after 5 second delay
    let playBtn = UIButton(type: .custom)
    playBtn.setImage(UIImage(named: countDown[4]), for: .normal)


}

1 个答案:

答案 0 :(得分:0)

这段代码有很多问题。我会尝试解释,但你真的应该回到你的书,并实际学习基础知识。你的语法正确,但你所写的内容没有任何意义。

此外,由于您提到您认为这是最有效的方式:现在不要担心性能或效率。要学习的最重要的事情是编写正确的程序,当你得到它时你可能会担心效率。即便如此,也不要太担心,只有在实际测量性能后才应进行优化。

所以先来看看是不是错了

var countDown = ["buttonThree", "buttonTwo", "buttonOne", "buttonRelease", "buttonMain"]

除非您打算更改此阵列,否则您应在此处使用let

//The Button in its initial state
@IBAction func playBtn(_ sender: UIButton) {

你的功能名称并不好。 Swift风格不使用Btn之类的缩写,您可以拼出Button。但即使playButton在这里也不是一个好名字,它没有说明这种方法有什么用处。更好的名称是playPushed或者只是play

    var imageCounter:Int = 0
    //Begin Loop
    while imageCounter != 3 {

虽然这种循环风格确实有效,并且它没有任何错误,但惯用的快速方式将使用for in循环。也是"开始循环"评论不是必需的,很明显,循环从此处开始 - 您可以从while关键字中看到它。

        let playBtn = UIButton(type: .custom)
        playBtn.setImage(UIImage(named: countDown[imageCounter]), for: .normal)

当然,您的按钮不会更改图像。在这里,您可以创建一个新按钮,设置它的图像并丢弃该按钮。除非将其添加到视图层次结构中,否则永远不会显示此按钮。但这在这里也没有意义 - 你想要改变当前按钮,而不是添加新按钮。

此处已有正确的按钮,这是sender参数。

        imageCounter = imageCounter + 1
        _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (timer) in }

定时器不能像这样工作 - 它们在经过一段时间后执行传递的闭包,它们不会在给定时间内暂停当前线程。你的闭包({ (timer) in }部分)什么也没做。

        print("Working")
    }
    //Delay on third iteration
    if imageCounter == 3 {
        //Execute recording
        _ = DispatchTime.now() + 5

这也不符合您的想法。这不是延迟,它只计算未来5秒的时间点,然后通过_ =部分忽略。

    }

    //Return to normal state after 5 second delay
    let playBtn = UIButton(type: .custom)
    playBtn.setImage(UIImage(named: countDown[4]), for: .normal)

同样,您不是在更新UI中的按钮,而是创建一个新的按钮,然后将其丢弃。

}

那么我们如何解决这个问题?

有很多方法可以写出来。保持代码形状的最简单方法是在循环中启动多个计时器,每次更改都有不同的延迟。

@IBAction
func playPushed(_ sender: UIButton) {

    sender.isEnabled = false
    let lastCountDownButton = 3
    for (offset, imageName) in countdown[0...lastCountDownButton].enumerated() {
        _ = Timer.scheduledTimer(withTimeInterval: TimeInterval(offset), repeats: false) { _ in
            sender.setImage(UIImage(named: imageName), for: .normal)

            if offset == lastCountDownButton {
                // Execute recording

                _ = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { _ in
                    sender.setImage(UIImage(named: countdown[4]), for: .normal)
                    sender.isEnabled = true
                }
            }
        }
    }
}

我做了一个小改动,同时禁用按钮,直到整个过程完成。否则,用户可能会多次启动整个序列,从而导致意外行为。

我还使用while循环代替for in循环来生成子阵列countdown[0...lastCountDownButton]。除了实际项目之外,enumerated()函数返回一个包含偏移量(从0开始计算)的新序列。

在一个真实的应用程序中,我会写这个不同的。我将整个逻辑封装在一个由单个计时器驱动的状态机中。