Edward Hasted Edward Hasted - 2 months ago 25
Swift Question

Swift - Press Button (completion handler issue)

I want a user to press a button, it changes background color (to yellow), a WAV is played and on completion of the WAV the button reverts to its original color (to red). So have a completion handler around the sound. Have tried various combinations of the code below but the WAV plays and the button doesn't appear to change color.

Is this the wrong approach or am I doing something wrong? Don't want to have to put completion handlers around the color changes as that, I presume, is overkill.

Many thanks.

typealias CompletionHandler = (success:Bool) -> Void

@IBAction func fireButton(sender: AnyObject) {
playLaser( { (success)-> Void in
if success {
self.shots -= 1
self.labelShotsLeft.text = String(self.shots)
} else {

}
})
}

func playLaser(completionHandler: CompletionHandler) {
fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error as NSError {
print(error.description)
}
self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
completionHandler(success: true)
}

Answer

This is one of those questions which is a little more subtle than meets the eye. I tried putting three completion handlers around each task: change colour to yellow, play sound, change colour back to red. The code was being executed in the correct sequence as I NSLogged it but the button never changed colour due to screen updating controls. Here is the code that works that I hope other readers might find useful:

Swift 2.0

@IBAction func fireButton(sender: AnyObject) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        dispatch_sync(dispatch_get_main_queue()) {
            self.fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
        }
        self.playLaser( { (success)-> Void in
            if success {
                self.shots -= 1
            } else {
            }
        })
        dispatch_sync(dispatch_get_main_queue()) {
            self.labelShotsLeft.text = String(self.shots)
            self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
        }
    }
}

func playLaser(completion: (success: Bool) -> ()) {
    let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
    do {
        player = try AVAudioPlayer(contentsOfURL: url)
        guard let player = player else { return }
        player.play()
        completion(success: true)
        } catch let error as NSError {
        completion(success: false)
    }
}

Swift 3.0

@IBAction func fireButton(_ sender: AnyObject) {
    let fireQueue = DispatchQueue(label: "queueFirebutton")
    fireQueue.async {
        DispatchQueue.main.sync {
            self.fireButtonDisabled()
        }
        DispatchQueue.main.sync {
            self.playLaser()
            self.shots -= 1
            if self.shots <= 0 {
                self.shots = 0
            }
        }
        DispatchQueue.main.sync {
            if self.shots < 0 { self.shots = 0}
            self.labelShotsLeft.text = String(self.shots)
            sleep(1)
            self.fireButtonEnabled()
        }
    }
}

func playLaser() {
    let url = Bundle.main.url(forResource: "laser", withExtension: "wav")!
    do {
        player = try AVAudioPlayer(contentsOf: url)
        guard let player = player else { return }
        player.play()
       // completion(true)
    } catch {
       // completion(false)
    }
}