How do I cancel a delayed action if the same button that triggered it is clicked before the delayed time is executed? - ios

class viewcontroller1: UIViewController {
I have these two images. When the next button is clicked, image1 changes to image2 after 10 seconds. However, I have no idea how to cancel/reset the delayed action if the next button is clicked again before the 10 seconds has expired. This is the code I have so far...
var image1: UIImageView!
var image2: UIImageView!
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
#IBAction func nextbutton(_ sender: Any) {
image1.image = UIImage(named: "image1")
delay(10) { self.image2.image = UIImage(named: "image2")
}
}
}
I would really appreciate any help. Thanks

anyncAfter can not be canceled
As per comment you have to use Timer following is example
Create global variable
var timer:Timer?
and On action
#IBAction func btnImageChangedTapped(_ sender: UIButton) {
if (self.timer != nil) {
self.timer?.invalidate()
self.timer = nil;
}
timer = Timer.scheduledTimer(timeInterval: yourTime, target: self, selector: #selector(changeImage:), userInfo: sender, repeats: false)
}

u can try this one
var timer : Timer?
func startTimer(){
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(self.YourImageChangeAction), userInfo: nil, repeats: false)
}
func resetTimer(){
timer?.invalidate()
startTimer()
}
add func resetTimer to your UiButton sender.

You could use a click counter in your VC and test it upon execution of the delayed code.
for example:
var clickCount = 0
#IBAction func nextbutton(_ sender: Any)
{
clickCount += 1
let wantsClickCount = clickCount
image1.image = UIImage(named: "image1")
delay(10)
{
guard self.clickCount == wantsClickCount
else { return }
self.image2.image = UIImage(named: "image2")
}
}
if the button was clicked again before the closure executes, the clickCount will no longer match and the timed code will do nothing.
This would even work with very fast clicking. Note that it will push off the image change by 10 seconds each time so the image change will always happen 10 seconds after the last click.

Related

Unexpected Output: I am using the below code to print out the timer according to the button pressed but timer is starting with 20 only. what's wrong?

import UIKit
class ViewController: UIViewController {
let eggTimes = ["Soft": 60,"Medium": 72,"Hard": 95]
var secondsRemaining = 20
var timer = Timer()
#IBAction func hardnessSelected(_ sender: UIButton) {
let hardness = sender.currentTitle!
var secondsRemaining = eggTimes[hardness]!
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
#objc func timerAction()
{
if secondsRemaining > 0 {
print("\(secondsRemaining) seconds")
secondsRemaining -= 1
}
}
}
Unexpected Output: I am using the below code to print out the timer according to the button pressed but timer is starting with 20 only. what's wrong?
You are creating a new variable inside your Button function:
var secondsRemaining = eggTimes[hardness]!
instead you should assing your value. It should be:
self.secondsRemaining = eggTimes[hardness]!

Loop vibration in Swift

I'm playing a sound using AVPlayer. I'm trying to let the iPhone vibrate during this sound.
I'm able to create a vibration with:
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
But I'm having problems trying to loop this vibration if I use:
vibrationTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.loopVibration), userInfo: nil, repeats: true)
#objc func loopVibration() {
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
}
The func loopVibration() gets called every two seconds but it doesn't vibrate.
Not sure why this isn't working for you--the following sample works for me on my test device (iPhone X, iOS 11.2).
The ViewController sample below includes outlets for playing single sounds, playing single vibrations, looping sounds and looping vibrations. The "Chirp" sound is a 1s wav file.
Note that since this is a sample, the code below doesn't dispose of the system sounds, nor invalidate timers if the ViewController is hidden. Make sure to manage your system resources appropriately if you adapt this.
//
// ViewController.swift
//
import UIKit
import AudioToolbox
class ViewController: UIViewController {
static let soundId: SystemSoundID? = {
guard let soundURL = Bundle.main.url(forResource: "Chirp", withExtension: "wav") else {
return nil
}
var soundId: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &soundId)
return soundId
}()
static let timerInterval: TimeInterval = 2
var soundTimer: Timer?
var vibrationTimer: Timer?
var playCount = 0 {
didSet {
playCountLabel.text = String(playCount)
}
}
func invalidateTimers() {
if let vibrationTimer = vibrationTimer {
vibrationTimer.invalidate()
}
if let soundTimer = soundTimer {
soundTimer.invalidate()
}
}
#IBOutlet weak var playCountLabel: UILabel!
#IBAction func playSound(_ sender: Any) {
if let soundId = ViewController.soundId {
AudioServicesPlaySystemSound(soundId)
playCount += 1
}
}
#IBAction func loopSound(_ sender: Any) {
invalidateTimers()
soundTimer = Timer.scheduledTimer(timeInterval: ViewController.timerInterval, target: self, selector: #selector(playSound(_:)), userInfo: nil, repeats: true)
}
#IBAction func vibrate(_ sender: Any) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
playCount += 1
}
#IBAction func loopVibrate(_ sender: Any) {
invalidateTimers()
soundTimer = Timer.scheduledTimer(timeInterval: ViewController.timerInterval, target: self, selector: #selector(vibrate(_:)), userInfo: nil, repeats: true)
}
#IBAction func cancelAll(_ sender: Any) {
invalidateTimers()
}
}
It's not permitted to use continuous vibration, your app might be rejected. If you still want to do it you can try something like this:
func vibrate() {
for i in 0...13 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(Double(i) * 0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
self.vibrate()
})
}
}

Continuously check for response in Swift

I have a boolean variable called flag with initial value of false. Based on a successful process, it's set to true. There is a button alert, when tap it, it checks for flag's value along with a spinning image on UI, if flag is true, then a success message should displayed. otherwise, it should keep continuing response check (ten times for 5 seconds).
This is my functionality. I've been using NStimer to achieve this. Here is the code snippet:
var timer = NSTimer()
var count = 10
var flag: Bool = false
#IBOutlet weak var alert: UIButton!
#IBAction func alertAction(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: #selector(ViewController.prints), userInfo: nil, repeats: true)
}
func prints(){
if(count > 0)
{
if flag == false{
**Spinning Image**
count -= 1
} else {
count = 0
}
} else {
timer.invalidate()
}
}
The spinning image stops and continues after every 5 seconds ( in case response takes more than 5 seconds). I wish to spin the image continuously without a break. Can someone please help?
Thanks in advance!
Polling is the most desperate asynchronous pattern and almost always wrong. Learn a bit about value observation and reactive pattern.
var flag = false {
didSet {
if flag {
// stop spinning
}
}
}
func alertAction() {
// start spinning
}
From what I understand, this code will do what you intend. If you are using a UIActivityIndicator. Ensure to start it where I started the rotationAnimation and stop it when invalidating your timer.
Swift 3 Example
#IBOutlet weak var pin: UIImageView!
var timer: Timer?
var count: Int = 5
var flag: Bool {
return count == 0
}
#IBAction func buttonPressed(_ sender: AnyObject) {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0
rotationAnimation.toValue = 2 * M_PI
rotationAnimation.duration = 0.6
rotationAnimation.isCumulative = true
rotationAnimation.repeatCount = Float.infinity
pin.layer.add(rotationAnimation, forKey: "rotate")
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(prints), userInfo: nil, repeats: true)
}
func prints() {
if flag {
pin.layer.removeAllAnimations()
timer?.invalidate()
} else {
count = count - 1
}
}

Stop watch working fine only first time in Swift

I made a stopwatch in swift. It has three buttons- play, stop, reset. it has a display in the middle where I display the seconds passed.
My code runs fine. But the watch runs very fast after the stop or reset.
I am not able to figure out what is the fuss.
please help!
var time = 0
var timer = NSTimer()
var pause = Bool(false)
#IBOutlet var result: UILabel!
func displayResult() {
if pause != true
{
time++
result.text = String(time)
}
}
#IBAction func reset(sender: AnyObject) {
time = 0
result.text = String(time)
}
#IBAction func pause(sender: AnyObject) {
pause = true
}
#IBAction func play(sender: AnyObject) {
pause = false
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("displayResult") , userInfo: nil, repeats: true)
}
Your pause action should be:
#IBAction func pause(sender: AnyObject) {
timer.invalidate()
pause = true
}
UPD:
var time = 0
var timer = NSTimer()
var pause = true // 1
func displayResult() {
if pause != true
{
time++
label.text = String(time)
}
}
#IBAction func reset(sender: AnyObject) {
time = 0
label.text = String(time)
timer.invalidate() // 2
pause = true // 3
}
#IBAction func pause(sender: AnyObject) {
timer.invalidate() // 4
pause = true
}
#IBAction func play(sender: AnyObject) {
// 5
if pause == true {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("displayResult") , userInfo: nil, repeats: true)
pause = false
}
}

How to reset NSTimer? swift code

So I'm creating an app/game where you tap a button corresponding to a picture before the timer runs out and you lose. You get 1 second to tap the button, and if you choose the right button then the timer resets and a new picture comes up. I'm having trouble reseting the timer. It fires after one second even after I attempt to reset it. Here's the code:
loadPicture() runs off viewDidLoad()
func loadPicture() {
//check if repeat picture
secondInt = randomInt
randomInt = Int(arc4random_uniform(24))
if secondInt != randomInt {
pictureName = String(self.PicList[randomInt])
image = UIImage(named: pictureName)
self.picture.image = image
timer.invalidate()
resetTimer()
}
else{
loadPicture()
}
}
and here's the resetTimer() method:
func resetTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("gameOverTimer"), userInfo: nil, repeats: false)
}
I think it may have something to do with NSRunloops? I'm not sure. I don't even know what a NSRunloop is to be honest.
So I finally figured it out...
I had to make a separate function to start the timer by initializing it.
And in the resetTimer() function I added the timer.invalidate() line. So my code looks like this:
func loadPicture() {
//check if repeat picture
secondInt = randomInt
randomInt = Int(arc4random_uniform(24))
if secondInt != randomInt {
pictureName = String(self.PicList[randomInt])
image = UIImage(named: pictureName)
self.picture.image = image
resetTimer()
}
else{
loadPicture()
}
}
func startTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("gameOverTimer"), userInfo: "timer", repeats: true)
}
func resetTimer(){
timer.invalidate()
startTimer()
}
EDIT
With the new selector syntax it looks like this:
func startTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(NameOfClass.startTimer), userInfo: "timer", repeats: true)
}

Resources