iOS Swift - Prevent Timer #fire() on #invalidate() - ios

Given the following code
private var myTimer: Timer? = nil
override func viewDidLoad() {
super.viewDidLoad()
// start my Timer
startMyTimer()
// stop timer and restart
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
self.myTimer!.fire()
print("my timer: invalidating \(Date.now.mediumTimeLocalizedDescription)")
self.myTimer!.invalidate()
self.myTimer = nil
self.startMyTimer()
}
}
private func startMyTimer() {
myTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
print("my timer: Fired \(Date.now.mediumTimeLocalizedDescription)")
}
myTimer!.fire()
}
I get the following output:
my timer: Fired 14:27:14
my timer: Fired 14:27:17
my timer: invalidating 14:27:17
my timer: Fired 14:27:17
my timer: Fired 14:27:27
Expected output:
my timer: Fired 14:27:14
my timer: Fired 14:27:17
my timer: invalidating 14:27:17
my timer: Fired 14:27:27
As you can see when I invalidate the timer fires right away!
Is there any way to prevent that?
I looked Apple documentation and it states "Stops the timer from ever firing again and requests its removal from its run loop." which I interpret as "When you invalidate, the timer does not fire anymore". Am I wrong?

You call startMyTimer() again in DispatchQueue.main.asyncAfter completion. In this function you initialize timer.

Related

swift 3 check if timer running

I am writing timer functions to check if it is currently running, But It seems that I always found the timer variable to be nil. So that it keeps on creating a new timer instance.
weak var myTimer: Timer?
// starting a timer
func startTimer(timer: Timer?, interval: Double, _selector: Selector, _repeats: Bool){
weak var _timer = timer
if (_timer == nil){
_timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: _selector, userInfo: nil, repeats: _repeats)
}
}
// stopping a timer
func stopTimer(timer: Timer?){
var _timer = timer
if (_timer != nil){
_timer!.invalidate()
_timer = nil
}
}
Starting the timer by calling
self.startTimer(timer: self.myTimer, interval: 0.1, _selector: #selector(self.updateTimer), _repeats: true)
Somehow, even after the timer is started, it is still nil. So the second time the function is called, it will schedule another timer instance. And when I try to stop it, it won't be successful, as it was always nil. How can I solve this?
thanks
This is expected. You are assigning the new Timer instance to weak var inside your function, which will cause it to be deallocated when you exit function scope. The function's timer variable must be declared as inout
func startTimer(timer: inout Timer?, interval: Double, _selector: Selector, _repeats: Bool) {
if (timer == nil){
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: _selector, userInfo: nil, repeats: _repeats)
}
The same goes for your second function - you are assigning nil only to local variable inside function scope, not to the variable outside.

Check if Timer is running

I have been trying to refill in game lives with a timer, but whenever I leave a view and return, the timer duplicates and becomes faster. I have tried to address this with the Timer?.isValid function to only run the timer when it is invalid so it never duplicates, but it cannot seem to check is the timer is invalid in an if statement.
This is the if statement that I have been using so far:
if (myTimer?.isValid){
} else{
//start timer
}
Any help would be appreciated, thanks.
I recommend to use self-checking startTimer() and stopTimer() functions, the timer will be started only if it's not currently running, the stop function sets the timer variable reliably to nil.
var timer : Timer?
func startTimer()
{
if timer == nil {
timer = Timer.scheduledTimer...
}
}
func stopTimer()
{
if timer != nil {
timer!.invalidate()
timer = nil
}
}
You need to check if your Timer instance isValid (not the Timer class), let's say: if myTimer.isValid? {}.

How Do I Cancel and Restart a Timed Event in Swift?

I have a sliderValueChange function which updates a UILabel's text. I want for it to have a time limit until it clears the label's text, but I also want this "timed clear" action to be cancelled & restarted or delayed whenever the UISlider is moved within the time limit before the "timed clear" action takes place.
So far this is what I have:
let task = DispatchWorkItem {
consoleLabel.text = ""
}
func volumeSliderValueChange(sender: UISlider) {
task.cancel()
let senderValue = String(format: "%.2f", sender.value)
consoleLabel.text = "Volume: \(senderValue)"
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3, execute: task)
}
Obviously, this approach does not work, since cancel() apparently cannot be reversed.. (or at least I don't know how). I also don't know how to start a new task at the end of this function which will be cancelled if the function is recalled..
Am I going about this the wrong way? Is there something I am overlooking to make this work?
Use a timer:
weak var clearTimer: Timer?
And:
override func viewDidLoad() {
super.viewDidLoad()
startClearTimer()
}
func startClearTimer() {
clearTimer = Timer.scheduledTimer(
timeInterval: 3.0,
target: self,
selector: #selector(clearLabel(_:)),
userInfo: nil,
repeats: false)
}
func clearLabel(_ timer: Timer) {
label.text = ""
}
func volumeSliderValueChange(sender: UISlider) {
clearTimer?.invalidate() //Kill the timer
//do whatever you need to do with the slider value
startClearTimer() //Start a new timer
}
The problem is that you are cancelling the wrong thing. You don't want to cancel the task; you want to cancel the countdown which you got going when you said asyncAfter.
So use a DispatchTimer or an NSTimer (now called a Timer in Swift). Those are counters-down that can be cancelled. And then you can start counting again.

WKInterfaceTimer used as a timer to countdown start and stop

I am trying to create a timer to countdown x minutes and y seconds.
I am computing the number of seconds and creating the InterfaceTimer like this:
timer.setDate(NSDate(timeIntervalSinceNow:Double(secondsValue+1)))
timer.stop()
after that I keep stoping it and starting it again and again, but the values are suddenly decreasing as "time(now) doesn't stop".
Eg: if the timer shows :55, I start it for 3sec and stop it, it shows :52, I wait 10seconds and then start it again, it starts from :42.
I can not save the value currently in the WKInterfaceTimer, so that I could start again from the same point. Everything I tried doesn't work. Did anyone work with the timer and it stayed at the same value after stopping it?
Yes the watchkit timer is a bit...awkward...and definitely not very intuitive. But that's just my opinion
You'll have to keep setting the date/timer each time the user chooses to resume the timer.
Remember, you'll also need an internal NSTimer to keep track of things since the current WatchKit timer is simply for display without having any real logic attached to it.
So maybe something like this...It's not elegant. But it works
#IBOutlet weak var WKTimer: WKInterfaceTimer! //watchkit timer that the user will see
var myTimer : NSTimer? //internal timer to keep track
var isPaused = false //flag to determine if it is paused or not
var elapsedTime : NSTimeInterval = 0.0 //time that has passed between pause/resume
var startTime = NSDate()
var duration : NSTimeInterval = 45.0 //arbitrary number. 45 seconds
override func willActivate(){
super.willActivate()
myTimer = NSTimer.scheduledTimerWithTimeInterval(duration, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)
WKTimer.setDate(NSDate(timeIntervalSinceNow: duration ))
WKTimer.start()
}
#IBAction func pauseResumePressed() {
//timer is paused. so unpause it and resume countdown
if isPaused{
isPaused = false
myTimer = NSTimer.scheduledTimerWithTimeInterval(duration - elapsedTime, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)
WKTimer.setDate(NSDate(timeIntervalSinceNow: duration - elapsedTime))
WKTimer.start()
startTime = NSDate()
pauseResumeButton.setTitle("Pause")
}
//pause the timer
else{
isPaused = true
//get how much time has passed before they paused it
let paused = NSDate()
elapsedTime += paused.timeIntervalSinceDate(startTime)
//stop watchkit timer on the screen
WKTimer.stop()
//stop the ticking of the internal timer
myTimer!.invalidate()
//do whatever UI changes you need to
pauseResumeButton.setTitle("Resume")
}
}
func timerDone(){
//timer done counting down
}

Make a service call at regular interval of time in swift

I am new to swift programming and I don't know how to call a method at regular interval of time. I have a demo app for service call but i don't know how can i call it at regular interval of time.
You can create an object of NSTimer() and call a function on definite time interval like this:
var updateTimer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: "callFunction", userInfo: nil, repeats: true)
this will call callFunction() every 15 sec.
func callFunction(){
print("function called")
}
Here is a simple example with start and stop functions:
private let kTimeoutInSeconds:NSTimeInterval = 60
private var timer: NSTimer?
func startFetching() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(kTimeoutInSeconds,
target:self,
selector:Selector("fetch"),
userInfo:nil,
repeats:true)
}
func stopFetching() {
self.timer!.invalidate()
}
func fetch() {
println("Fetch called!")
}
If you get an unrecognized selector exception, make sure your class inherits from NSObject or else the timer's selector won't find the function!
Timer variant with a block (iOS 10, Swift 4)
let timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { (timer) in
print("I am called every 5 seconds")
}
Do not forget call invalidate method
timer.invalidate()
GCD approach (will tend to drift a bit late over time)
func repeatMeWithGCD() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) {
print("I am called every 5 seconds")
self.repeatMeWithGCD()//recursive call
}
}
Do not forget to create a return condition to prevent stackoverflow error

Resources