Quick Context: I am building a game where players listen to a song and have to press a button every 4th beat. I am trying to use a timer that maintains the beat of the song. I used NSTimer, however the time tends to get off beat after a while. I also tried GCD, however the timer also got off beat. Is there a way to make a timer that always maintains its paste, like a metronome?
GCD:
//GCD example that did not work
var timerf: DispatchSourceTimer!
func handleStartTimer() {
let queue = DispatchQueue(label: "com.domain.app.timer", qos: .userInteractive)
timerf = DispatchSource.makeTimerSource(flags: .strict, queue: queue)
timerf.scheduleRepeating(deadline: .now(), interval: 0.585, leeway: .nanoseconds(0))
timerf.setEventHandler {
self.handleAddSecondtoTime()
}
timerf.resume()
//NSTimer example that did not work
timer = Timer.scheduledTimer(timeInterval: 0.585, target: self, selector: #selector(self.handleAddSecondtoTime), userInfo: nil, repeats: true)
}
Related
I have scheduled timers that add sprite nodes to the screen as obstacles
func timers(){
personTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(spawnPerson), userInfo: nil, repeats: true)
bikeTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(spawnBike), userInfo: nil, repeats: true)
motorcycleTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(spawnMotorcycle), userInfo: nil, repeats: true)
}
I added a function to invalidate those timers. so that a bonus "level" can be ran.
func invalidateTimers(){
// Obstacles
personTimer.invalidate()
bikeTimer.invalidate()
motorcycleTimer.invalidate()
}
When the bonus is called
func bonus() {
invalidateTimers()
bonusTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(spawnDiamonds), userInfo: nil, repeats: true)
}
The problem that I'm having is that when the bonus is done running I invalidate the bonusTimer and recall timers(). But when I do all the timers in the function seem to be firing twice. Whats an easy workaround for that since they can't just be paused.
Instead of using timers, consider using SKActions, as they work well with SpriteKit. To start the timer, run:
let wait1 = SKAction.wait(forDuration: 1)
let personTimer = SKAction.repeatForever(SKAction.sequence([wait1, SKAction.run {
spawnPerson() // spawnBike() etc. for each different timer
}]))
self.run(personTimer, withKey: "spawnPerson")
with modified wait values and function calls for each different timer. Then to stop the timer, run:
self.removeAction(forKey: "spawnPerson")
for each action using a different key.
Instead of using timers, you can use update: method of your SKScene subclass. It calls once per frame and has currentTime parameter. So you can easily calculate time intervals you need and trigger corresponding methods.
I implemented Background Fetch feature in my App.
The function called by background fetch includes a delayed function.
However this function doesn't seem to trigger :
let when = DispatchTime.now() + 2
DispatchQueue.main.asyncAfter(deadline: when) {
MyFunction()
}
It seems to trigger however as soon as I wake up the app.
Why this doesn't trigger on background fetch ?
Try this code:
DispatchQueue.global().async {
let timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.MyFunction), userInfo: nil, repeats: false)
RunLoop.current.add(timer, forMode: .defaultRunLoopMode)
RunLoop.current.run()
}
I don't think you can run in the background on the main queue. Try this (changed main to global()):
let when = DispatchTime.now() + 2
DispatchQueue.global().asyncAfter(deadline: when) {
MyFunction()
}
I'm trying to add a countdown timer to an existing app. Naturally, I don't want this timer to stall the rest of the application so I wanted to use an asynchronous thread dedicated to the timer. This is my current code and it doesn't even get to the update function (I used print statements to test this), but does print "Got". Also, I'm trying to update a label with the correct time and you can't do that within the thread. The time variable is a class variable. Not sure if this is even the correct approach, any suggestions?
Edit: Running the timer on main queue doesn't work as it interferes with a pan gesture I already have on the app. Also, any proposed solutions to the timing inaccuracies of the Timer Class would also be great.
func startTimer() {
time = 30
let queue = DispatchQueue(label: "timer")
queue.async {
print("Got")
_ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
}
func update() {
DispatchQueue.main.sync {
if(time >= 0) {
time -= 1
timer.text = String(time)
} else {
timer.text = "0"
}
}
}
The following code snippet works perfectly when called outside a completion block, but the timer is never fired when I set it up inside the block. I don't understand why there is a difference:
self.timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.foo),
userInfo: nil,
repeats: true)
I was not using the self references when calling it initially outside the block, but then once inside, it was required. However I tested the exact same code outside the block again and it does still work.
The block is a completion hander that is called after asking permission for HealthKit related information.
The issue is that the completion block in question was probably not running on the main thread and therefore didn't have a run loop. But timers need to be scheduled on a run loop, and while the main thread has one, most background threads do not (unless you add one, yourself).
To fix this, in that completion handler, dispatch the creation of the timer back to the main thread and it should work fine:
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)
}
Or use a dispatch source timer (a timer that can be scheduled for a background queue, and doesn't require a run loop).
var timer: DispatchSourceTimer!
private func startTimer() {
let queue = DispatchQueue(label: "com.domain.app.timer")
timer = DispatchSource.makeTimerSource(queue: queue)
timer.setEventHandler { [weak self] in
// do something
}
timer.schedule(deadline: .now(), repeating: 1.0)
timer.resume()
}
For syntax for earlier version of Swift, see previous revision of this answer.
Another reason why Timer() might not work work is how it's created. I had the same problem, and everything I tried didn't solve it, including instantiating on the main thread. I stared at this for quite a while until I realized (stupidly) that I was creating it differently. Instead of Timer.scheduledTimer
I instantiated it using
let timer = Timer(timeInterval: 4.0, target: self, selector: #selector(self.timerCompletion), userInfo: nil, repeats: true)
In my case I had to actually add it to a run loop to get it to run. Like this
RunLoop.main.add(timer, forMode: RunLoop.Mode.default)
This may sound obvious, but I had a similar problem, the timer just wouldn't fire and the reason is that it wasn't in the main thread...No errors, just never fired.
Put in the main thread and at least you have a shot at it!
DispatchQueue.main.async {
//insert your timer here
}
I am trying to use the NSTimer to increment the progress bar in my app when recording voice (see the screenshot)
let timedClock = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
internal func Counting(timer: NSTimer!) {
if timeCount == 0 {
//self.timedClock = nil
stopRecording(self) //performs segue to another view controller
} else {
timeCount--;
self.timer.text = "\(timeCount)"
}
print("counting called!")
progressBar.progress += 0.2
}
The progress bar works only for the first time after I compile and run the project. When the recording is finished, the app performs segue to another view controller to play the recorded audio. However, when I go back to the view for recording, the timer/progress bar automatically runs. I suspect the NSTimer object is still alive on the NSRunLoop. So I was wondering how to prevent the NSTimer from automatically running.
Inspired by the answer in this SO thread, I tried the following, but the NSTimer still automatically runs.
let timedClock = NSTimer(timeInterval: 1, target: self, selector: "Counting:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timedClock, forMode: NSRunLoopCommonModes)
This happens because when your controller created it's properties are automatically initialized. According to Apple Docs (and method's name) scheduledTimerWithTimeInterval create and return scheduled timer. So if you only want create your timer and call it by trigger function use it like this:
class MyClass {
var timer: NSTimer?
...
func enableTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
}
func disableTimer() {
timer?.invalidate()
timer = nil
}
...
}
Sorry for the quick self-answer, as I just found out that I can use the invalidate() method to prevent the timer from automatically firing:
timedClock.invalidate()
Hope it helps someone in the future!