Sleep or delay a Timer thread in Swift - swift

I have been trying to create a delay of one or two seconds on a self repeating timer. This is how I create the timer:
currentThread = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(Movement.updatePosition), userInfo: nil, repeats: true)
So the timer constantly runs the method updatePosition(). However, I have an if statement within that method where I would like to have the timer be delayed for a few seconds:
if distance <= respawnDistance * 0.1 {
// Delay timer for 1 second
}
And I was thinking that I could do this:
currentThread.invalidate()
And then just create another Timer that runs after 1 second, which leads to the reactivation of the previous timer. However, I think that would be inefficient if there is a way to sleep the current Timer?

NSTimer is not that accurate. Its maximum resolution is somewhere around 50 - 100ms. Anyhow, you can add a variable to control the firing of the timer:
var doNotUpdate = false
if distance <= respawnDistance * 0.1 {
doNotUpdate = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
doNotUpdate = false
}
}
func updatePosition() {
if doNotUpdate { return }
// update your position
}

Related

Create a timer that pauses the code's flow Swift

Does a timer like let timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: false) actually stops the code for 10 seconds? Or will the code behind keeps running? I need a timer that could pause the code's flow for 10 seconds, and I don't want to use sleep or a closure! How can I implement this? Thanks.
You can probably use a Bool and the Timer in combination.
Take 2 Timers.
Timer1 - would at equal intervals call a method which internally checks the flag and would perform / not perform the action.
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
func fireTimer() {
if self.blockCode { return }
// your code here
}
Timer2 - would toggle the flag after a given set time, lets say 10 seconds.
self.blockCode = true
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
self?.blockCode = false
}
This would make you stop a code from execution for a fixed set of time without actually freezing the app like sleep would do.
Hope this helps.

Swift Timers in Loop all fire at the same time instead of sequentially

I am using a Swift Timer which works fine until I try putting it into a loop. I start the timer and after it reaches zero the selector calls a method to invalidate the timer. I want this to be repeated for 3 times and have a counter that counts the number of iterations this goes through.
func start() {
var interval = 0
repeat {
interval += 1
print ("Interval \(interval)")
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
} while (interval <= 3)
}
#objc func fireTimer() {
timeOn -= 1
if timeOn == 0 {
print("timer done")
timer?.invalidate()
}
}
The output is:
Interval 1
Interval 2
Interval 3
timer done
It seems as if 3 timers are started simultaneously and then the timer.invalidate stops all of them. What I want is to have the timers start independently and run sequentially. Notice the repeat/while loop. Any suggestions appreciated.
You said:
It seems as if 3 timers are started simultaneously and then the timer.invalidate stops all of them.
No. All three timers are running and when you call invalidate you're just invalidating the last one. Each time you set timer, you are discarding your reference to the prior one. Because you discarded your references to your first two timers, you now have no way to stop them. (Add a print statement in fireTimer and you will see the other two continue to fire after you cancel the one timer.) And because you invalidated on timer when timeOn was zero, the other two will keep firing, with timeOn now having negative values, and the == 0 test will never succeed again.
Instead, you could let your timer handler routine to accept a parameter, the timer reference. That way each one would be able to invalidate itself.
E.g.
func start() {
for interval in 0..< 3 { // if you really want three timers, then for loop is easiest
print ("Interval \(interval)")
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fireTimer(_:)), userInfo: nil, repeats: true)
}
}
#objc func fireTimer(_ timer: Timer) {
print(Date(), "tick")
timeOn -= 1
if timeOn <= 0 {
print("timer done")
timer.invalidate()
}
}
Now that is exceedingly confusing have multiple repeating timers all updating the same timeOn property. I changed the if test to be <= 0 to address that problem.
It begs the question why you would want multiple repeating timers firing at basically the same time. E.g. every second, timeOn is being reduced by three. Is that really the intent? Generally you would only want one repeating timer.
This process of scheduling a bunch of timers also begs the question of how you will cancel them when the object in question is deallocated. I guess you could keep an array of them, but it seems very convoluted way of doing it. The other approach is to use the block-based timer with [weak self] reference (to prevent strong reference cycle), and then each can check to see if self is nil and if so, invalidate itself:
func start() {
for interval in 0..< 3 { // if you really want three timers, then for loop is easiest
print ("Interval \(interval)")
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
self.fireTimer(timer, interval: interval)
}
}
}
func fireTimer(_ timer: Timer, interval: Int) {
print(Date(), "tick", interval)
timeOn -= 1
if timeOn <= 0 {
print("timer done")
timer.invalidate()
}
}
But I am unclear why you would want multiple repeating timers at all.
You said:
I don’t really want 3 timers. I want the timer block to run three times, sequentially.
Then just create a repeating timer that will run three times and then invalidate itself:
weak var timer: Timer? // weak because when you schedule the timer, the RunLoop keeps a strong reference
deinit {
timer?.invalidate() // in case the timer is still running after you dismiss this object/controller
}
func start() {
timer?.invalidate() // in case you accidentally called this previously, cancel any prior timer (before you lose a reference to it)
var counter = 0
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
counter += 1
if counter >= 3 { timer.invalidate() }
self?.doSomething(counter) // do whatever you want here
}
}
Key things to note:
I used single repeating timer.
I used closure based rendition with [weak self] to avoid the strong reference cycle of the selector-based rendition of Timer.
If you’d like to keep a reference to the timer so that you can invalidate it as soon as the parent object is deallocated, keep your own weak reference to it.
In start, I invalidate any prior timer, which is obviously unnecessary if you make sure that you call start once and only once. But, again, it is a solid defensive programming pattern to invalidate any prior timer before you replace any old reference with a new Timer reference.

Changing the time interval of a Timer in swift spritekit

So, I have this timer that is setup to run a specific function (which are both shown below) on a time interval variable called 'frequency' when I try and change the timeinterval variable frequency to a lower number based on the score number it doesn't seem to change the rate at which it fires it just seems to fire at the same time even if the frequency is changed to a lower number
override func didMove(to view: SKView) {
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
func spawnFallingOjects() {
if (GameState.current == .playing || GameState.current == .blackstone) {
guard usingThirdEye == false else { return }
let scoreLabel = childNode(withName: "scoreLabel") as! Score
let lane = [-100, -50 , 0, 50, 100]
let duration = 3.0
switch scoreLabel.number {
case 0...50:
frequency = 6.0
print("frequency has changed: \(frequency)")
case 51...100:
frequency = 4.5
print("frequency has changed: \(frequency)")
case 101...200000:
frequency = 1.1
print("frequency has changed: \(frequency)")
default:
return
}
let randomX = lane[Int(arc4random_uniform(UInt32(lane.count)))]
let object:Object = Object()
object.createFallingObject()
object.position = CGPoint(x: CGFloat(randomX), y: self.size.height)
object.zPosition = 20000
addChild(object)
let action = SKAction.moveTo(y: -450, duration: duration)
object.run(SKAction.repeatForever(action))
}
}
How do I make the timer fire faster when the frequency number changes to a lower number? should I recreate the timer at the end of the function?
You should actually avoid using Timer, Sprite kit has its own time functionality, and Timer does not work well with it and is a real pain to manage.
Instead, use SKAction's to wait and fire:
let spawnNode = SKNode()
override func didMove(to view: SKView) {
let wait = SKAction.wait(forDuration:frequency)
let spawn = SKAction.run(spawnFallingObjects)
spawnNode.run(SKAction.repeatForever(SKAction.sequence([wait,spawn])))
addChild(spawnNode)
}
Then to make it faster, just do:
switch scoreLabel.number {
case 0...50:
spawnNode.speed = 1
print("speed has changed: \(spawnNode.speed)")
case 51...100:
spawnNode.speed = 1.5
print("speed has changed: \(spawnNode.speed)")
case 101...200000:
spawnNode.speed = 2
print("speed has changed: \(spawnNode.speed)")
default:
return
}
The timeInterval property of Timer is a readonly property. (And your code is not trying to write a new frequency into the property...)
should I recreate the timer at the end of the function?
Nearly yes. Just you have no need to do it at the end.
With changing your method header like this:
func spawnFallingOjects(_ timer: Timer) {
You can access the fired Timer through the parameter timer, so you may need to write something like this just after switch scoreLabel.number {...}:
if frequency != timer.timeInterval {
//Invalidate old Timer...
timer.invalidate()
//And then allocate new one
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
You can modify the fireDate property of an existing Timer (in case which still isValid), but recreating a Timer instance is not a heavy operation (comparing to creating an SKSpriteNode instance), so recreating a new Timer seems to be a little bit easier.

Change the value of a variable after a set amount of time in Swift

I am making a game similar to pitfall in Swift and I am trying to make a boolean that shows whether the player is jumping or not. I want the boolean to become false after 3 seconds so that the player moves down again. I have tried using a delay function but it didn't work.
Thanks in advance.
Try this:
let delay = 3 // seconds to wait before firing
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(delay)) {
// set your var here
}
Replace DispatchQueue.main with whichever queue you're using.
The follow code snippet describes Player objects that have a isJumping property. When it's set to true (using didSet), it automatically starts a timer that after 3 seconds resets isJumping to false.
Please not that the snippet makes use of a NSTimer extensions for comfortably starting and handling the timer. Credits to https://gist.github.com/natecook1000/b0285b518576b22c4dc8
class Player {
private var resetJumpingTimer: NSTimer?
var isJumping: Bool = false {
didSet {
resetJumpingTimer?.invalidate() // Stops the timer in case it was already started
if isJumping {
self.resetJumpingTimer = NSTimer.schedule(3.0) { [weak self] _ in
self?.isJumping = false
}
}
}
}
}
extension NSTimer {
class func schedule(delay delay: NSTimeInterval, handler: NSTimer! -> Void) -> NSTimer {
let fireDate = delay + CFAbsoluteTimeGetCurrent()
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, 0, 0, 0, handler)
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes)
return timer
}
}
After the player jumps, you create an NSTimer.
Declare global variables let timer = NSTimer() and var seconds = 3
Then after the player jumps you set the timer:
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(YOUR_CLASS_NAME.updateTimer()), userInfo: nil, repeats: true)
Then the method:
func updateTimer() {
seconds -= 1
if seconds == 0 {
// Do stuff
timer.invalidate() // Stop the timer
seconds == 3 // Reset # of seconds
}
Give it a try.
func update() {
seconds -= 1
if seconds <= 0 {
score = 5
}
}
You may want to make it <= if you don't invalidate the timer. That way it stays the variable, etc.

Invalidating NSTimer not working

I have an NSTimer that is set up for 150 seconds, and when it gets down to 0, it should stop counting and end the game I'm creating. But, when I call the invalidate() it will just keep going. I also have it so that it should print "Timer stopped" when the timer is invalidated but no luck. Is there another way to to this?
Here is my code:
import SpriteKit
var countDown: NSTimer()
class GameScene: SKScene {
override func didMoveToView(view:SKView)
countDown = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "subtractTime", userInfo: nil, repeats: true)
}
override func update() {
if countDown == 0 {
countDown.invalidate()
print("Timer stopped")
}
This is because you compare countDown with Int value. If you need invalidate timer when it's fired - do it inside subtractTime method.
You also mentioned that you setup your timer for 150 seconds. But in code example it is 1.0. So I suggest you want your selector be called 150 times with one second delay. If so, you could simply add counter variable:
var counter = 0
...
func subtractTime() {
counter += 1
if counter == 150 {
countDown.invalidate()
countDown = nil // also add this line to escape retain cycle
return
}
...
}