How to setup dependent timers in swift? - swift

I have a timer that calls a function every second. However, I want to set up another timer that calls a function something around .3 of a second before. How would I set this up?

It would be much easier to use your timer to call the other, earlier function. Then add code to call your other function 0.3 seconds later.
// Called every second by the timer
func someTimerHandler(timer: Timer) {
// perform earlier function here
// Use another queue if desired
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
// perform later function here
}
}

Related

How to suspend a work item on the main queue

I want to know if it is possible to suspend and then resume a work item on the main queue whilst maintaining the '.asyncAfter' time. If not, is there a workaround to achieve this?
At a certain point, I queue up the following DispatchWorkItem:
dispatchWorkItem = DispatchWorkItem(qos: .userInteractive, block: {
self.view.backgroundColor = UIColor.workoutBackgroundColor
self.runTimer()
self.timerButton.animateableTrackLayer.removeAnimation(forKey: "strokeEndAnimation")
self.isRestState = false
})
I queue this up using:
DispatchQueue.main.asyncAfter(deadline: delayTime, execute: self.dispatchWorkItem))
(delayTime being a parameter to the function)
Now, the problem I am running into is how can I suspend this work item if the user performs a 'pause' action in my app.
I have tried using the DispatchQueue.main.suspend() method but the work item continues to execute after the specified delay time. From what I have read, this method should suspend the queue and this queued work item since it is not being executed. (Please correct me if I am wrong there!)
What I need to achieve is the work item is 'paused' until the user performs the 'resume' action in the app which will resume the work item from where the delay time left off.
This works on background queues that I have created when I do not need to make UI updates; however, on the main queue is appears to falter.
One workaround I have considered is when the user performs the pause action, storing the time left until the work item was going to be executed and re-adding the work item to the queue with that time on the resume action. This seems like a poor quality approach and I feel there is a more appropriate method to this.
On that, is it possible to create a background queue that on execution, executes a work item on the main queue?
Thanks in advance!
On that, is it possible to create a background queue that on execution, executes a work item on the main queue?
You are suggesting something like this:
var q = DispatchQueue(label: "myqueue")
func configAndStart(seconds:TimeInterval, handler:#escaping ()->Void) {
self.q.asyncAfter(deadline: .now() + seconds, execute: {
DispatchQueue.main.async(execute: handler())
})
}
func pause() {
self.q.suspend()
}
func resume() {
self.q.resume()
}
But my actual tests seem to show that that won't work as you desire; the countdown doesn't resume from where it was suspended.
One workaround I have considered is when the user performs the pause action, storing the time left until the work item was going to be executed and re-adding the work item to the queue with that time on the resume action. This seems like a poor quality approach and I feel there is a more appropriate method to this.
It isn't poor quality. There is no built-in mechanism for pausing a dispatch timer countdown, or for introspecting the timer, so if you want to do the whole thing on the main queue your only recourse is just what you said: maintain your own timer and the necessary state variables. Here is a rather silly mockup I hobbled together:
class PausableTimer {
var t : DispatchSourceTimer!
var d : Date!
var orig : TimeInterval = 0
var diff : TimeInterval = 0
var f : (()->Void)!
func configAndStart(seconds:TimeInterval, handler:#escaping ()->Void) {
orig = seconds
f = handler
t = DispatchSource.makeTimerSource()
t.schedule(deadline: DispatchTime.now()+orig, repeating: .never)
t.setEventHandler(handler: f)
d = Date()
t.resume()
}
func pause() {
t.cancel()
diff = Date().timeIntervalSince(d)
}
func resume() {
orig = orig-diff
t = DispatchSource.makeTimerSource()
t.schedule(deadline: DispatchTime.now()+orig, repeating: .never)
t.setEventHandler(handler: f)
t.resume()
}
}
That worked in my crude testing, and seems to be interruptible (pausable) as desired, but don't quote me; I didn't spend much time on it. The details are left as an exercise for the reader!

Increase Node Speed with SKPhysicsBody linearDamping Swift 4

I have the below class being called from my GameScene
func update(_ currentTime: TimeInterval)
Since it's being called there, my class is being called every second which leads me to this. I currently have my self.physicsBody?.linearDamping = 0.6 but how would I increase that number so I can also increase the speed? I was going to use another Timer till I realized my SKSpriteNode class is being called every second. Not too sure how to go about this, any ideas? I basically want to decrease that number every 2.0 seconds without letting the update function get in the way.
Any time you want to do something at regular time intervals in a sprite-Kit game, you can implement this as follows:
First declare 2 properties (in your class but outside all the function definitions)
var timeOfLastThing: CFTimeInterval = 0.0
var timePerThing: CFTimeInterval = 2.0 // How often to do the thing
( if the thing you want to do every 2 seconds is spawn a monster, then you might call these timeOfLastMonsterSpawn and timePerMonsterSpawn, but it's up to you)
Then, in Update, check to see if the timePerThing has been exceeded. If so, call your doThing function which does what you need it to and then reset the time since the last call:
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if (currentTime - timeOfLastThing > timePerThing) {
doThing()
self.timeOfLastThing = currentTime
}
}
func doThing() {
// Your spawn code here
}
The advantage of making the doThing a separate function (as opposed to it being in line in update())is that you can call it from didMoveToView or any other place to spawn objects outside of the normal time-controlled cycle.
You can change the value of timePerThing as necessary to control the rate at which things happen.
You could also look into creating an SKAction that runs doThing at specified time intervals, but I think to change the rate at which objects are spawned, you'll have to delete and re-create the SKAction, which you could do this in a setter for timePerThing.
You shouldn't really use NSTimer in SpriteKit, as the SpriteKit engine will be unaware of what the timer is doing and can't control it (one example is that the timer keeps running and doing stuff even if you set the scene to paused).

Timer not firing every second on WatchKit

This timer isn't firing every second, when I check the log and UI it seems to be firing every 3-4 seconds.
func startTimer() {
print("start timer")
timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(timerDidFire),
userInfo: nil,
repeats: true)
}
func timerDidFire(timer: Timer) {
print("timer")
updateLabels()
}
Is this just something that is going to happen on the Watch due to lack of capabilities, or is there something wrong in my code?
Here is the log if needed:
0.0396000146865845
3.99404102563858
7.97501903772354
11.9065310359001
EDIT:
And for clarification, what I'm updating every second is the workout timer, so it needs to be updated every second that ticks by.
If your app is busy doing something else, which blocks or delays the run loop from checking that the fire time has repeatedly passed, the timer will only fire once during that period:
A repeating timer always schedules itself based on the scheduled firing time, as opposed to the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.
As an aside, it may be more efficient to update your UI based on a response to a change (e.g., observation), or reaction to an event (e.g., completion handler).
This avoids creating busy work for the app when it's driven to check yet doesn't actually have a UI update to perform, if nothing has changed during the timer interval.
It also prevents multiple changes within the fire interval from being ignored, since a timer-driven pattern would only be displaying the last change in the UI.
Consider using a WKInterfaceTimer label in place of the label that you are using to show the timing:
A WKInterfaceTimer object is a special type of label that displays a
countdown or count-up timer. Use a timer object to configure the
amount of time and the appearance of the timer text. When you start
the timer, WatchKit updates the displayed text automatically on the
user’s Apple Watch without further interactions from your extension.
Apple Docs.
WatchOS will then take responsibility for keeping this up-to-date. The OS handles the label for you, but you have to keep track of the elapsed time: you just need to set an NSDate to do that (see example below).
Sample Code.
In your WKInterfaceController subclass:
// Hook up a reference to the timer.
#IBOutlet var workoutTimer: WKInterfaceTimer!
// Keep track of the time the workout started.
var workoutStartTime: NSDate?
func startWorkout() {
// To count up use 0.0 or less, otherwise the timer counts down.
workoutTimer.setDate(NSDate(timeIntervalSinceNow: 0.0))
workoutTimer.start()
self.workoutStartTime = NSDate()
}
func stopWorkout() {
workoutTimer.stop()
}
func workoutSecondsElapsed() -> NSTimeInterval? {
// If the timer hasn't been started then return nil
guard let startTime = self.workoutStartTime else {
return nil
}
// Time intervals from past dates are negative, so
// multiply by -1 to get the elapsed time.
return -1.0 * self.startTime.timeIntervalSinceNow
}
Comprehensive blog entry: here.
As of 2021, the (Foundation) Timer object supports a tolerance variable (measured in seconds). Set timer.tolerance = 0.2, and you should get a fire every second (+/- 0.2 seconds). If you are just updating your GUI, the exact time interval isn't that critical, but this should be more reliable than using no tolerance value. You'll need to create the timer separately, and manually add to the run queue such as below... (Swift)
import Foundation
// Set up timer to fire every second
let newTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
self.timerFired()
}
newTimer.tolerance = 0.2 // For visual updates, 0.2 is close enough
RunLoop.current.add(newTimer, forMode: .common)

In which cases runAction on SKNode does not complete?

Are there any known cases where running an SKAction using runAction does not complete?
I launch several 'runAction' on different SKNode. In order to synchronize all these actions, I use a counter that is incremented inside the completion block of each SKAction. When the counter reach the exact number of launched SKAction then the animations is completed.
From time to time one SKAction does not complete then the animation never complete.
// Several actions are launched...
myNode.runAction(myActions,completion:{
checkCompletion()
})
// Check if all actions completed
//
// numberOfLaunchedActions: number of actions launched
// logDebug: some log helper
func checkCompletion() {
// This counter is initialized earlier
numberOfCompletedActions++
logDebug(">> Actions completed: \(numberOfCompletedActions)/\(numberOfLaunchedActions)")
if numberOfCompletedActions == numberOfLaunchedActions {
/// some statements
logDebug("Animation Completed!")
}
}
Actions are dynamically generated and are composed of sequence of following actions:
waitForDuration
scaleTo
moveBy
hide
unhide
No removeFromParent nor runAction nor runBlock.
The action I focus my attention on is the following:
let waitAction = SKAction.waitForDuration(0.4)
let scaleAction = SKAction.scaleTo(0.1, duration: 2.0)
scaleAction.timingMode = .EaseOut
let myAction = SKAction.sequence([
waitAction,
scaleAction,
])
There is one known case: adding action after Remove from parent in a sequence: SKAction runAction does not execute completion block
As explained in comment:
Remove from parent is causing the rest of the actions in the
sequence not to be called, since the involved node is no longer in
the scene. The sequence didn't complete, therefore the completion
block shouldn't be called.

Delay 'glitch' with dispatch_after swift

Currently, i have a delay function as follows:
//Delay function from http://stackoverflow.com/questions/24034544/dispatch-after-gcd-in-swift/24318861#24318861
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
This code works for what i need, but as soon as the delay gets greater than 13 or so seconds, it seems to glitch out and stop delaying. Does anyone know a solution to this, or even while this is happening?
Here is the code in my use:
var delayTime = Double(1)
for number in self.gameOrder{
if number == 0{
delay(delayTime++){self.greenButton.highlighted = true}
self.delay(delayTime++){
self.greenButton.highlighted = false
}
}
else if number == 1{
delay(delayTime++){self.redButton.highlighted = true}
self.delay(delayTime++){
self.redButton.highlighted = false
}
}
else if number == 2{
delay(delayTime++){self.yellowButton.highlighted = true}
self.delay(delayTime++){
self.yellowButton.highlighted = false
}
}
else if number == 3{
delay(delayTime++){self.blueButton.highlighted = true}
self.delay(delayTime++){
self.blueButton.highlighted = false
}
}
println(delayTime)
}
}
}
Once delayTime gets to 13, the delay starts to play up.
Thanks!
You didn't say what platform/OS, but if on iOS, this behavior changed from iOS 7 to iOS 8. It would appear to be coalescing the timers (a power saving feature to group similar timer events together to minimize the power consumption).
The solution is to refactor the code either to use a single repeating timer or rather than scheduling all of the dispatch_after calls up front, have each dispatch_after trigger the next dispatch_after in its completion block (thus never having a bunch of dispatch_after calls pending at the same time that it might be coalesced together).
By the way, if using a repeating timer, you might want to use a dispatch source timer rather than a NSTimer, as this not only gives you the ability to specify the desired leeway, but the third parameter of dispatch_source_set_timer lets you specify a value of DISPATCH_TIMER_STRICT which:
Specifies that the system should make a best effort to strictly observe the
leeway value specified for the timer via dispatch_source_set_timer(), even
if that value is smaller than the default leeway value that would be applied
to the timer otherwise. A minimal amount of leeway will be applied to the
timer even if this flag is specified.
CAUTION: Use of this flag may override power-saving techniques employed by
the system and cause higher power consumption, so it must be used with care
and only when absolutely necessary.
In Mac OS X, this can be used to turn off "App Nap" feature (where timers will be more significantly altered in order to maximize battery life), but given the appearance of this timer coalescing in iOS, it might be a useful option here, too.