I have 2 actions that i put in a sequence. In the first action I am calling a method to calculate the new waiting time for the next action. The next action is just a wait for this duration, but the second action always executes straight away, so the time must be 0. I debugged it and in the method spawnFlowers i get the time returned as 3.5 seconds.
these are my 2 actions
let spawnFlowerAction = SKAction.run {
self.WaitTime = self.calculateWaitingTime()
}
let waitForNewFlower = SKAction.wait(forDuration: self.WaitTime)
I execute it this way:
let spawnSeq = SKAction.sequence([spawnFlowerAction, waitForNewFlower])
let spawnRepeat = SKAction.repeat(spawnSeq, count: 4)
self.run(spawnRepeat)
Result: 4 times spawned without waiting, printing 4 different calculated times in the console from the calculateWaitingTime function (in which the spawning happens)
What is a good way to fix this?
The problem is trying to dynamically change the values used within SKActions after the action has been created. For example when your WaitTime variable changes while running the spawnFlowerAction, the waitForNewFlower Action's wait time won't change dynamically because it doesn't reference WaitTime. Instead its wait value is whatever your variable WaitTime was when you declared let waitForNewFlower = SKAction.wait(forDuration: self.WaitTime) (Which I'm guessing was initially 0). Same concept goes with your other two spawn actions.
I usually use the dispatch Queue for things like these, but to use SKActions here's a function. Just call it once and input the number of times you want it to repeat.
func spawnRepeat(count: Int) {
//Put whatever code to spawn flower here
print("SPAWN FLOWER")
if count > 1 {
//Recalculate WaitTime
WaitTime = calculateWaitingTime()
let waitAction = SKAction.wait(forDuration: WaitTime)
run(waitAction, completion: { self.spawnRepeat(count: count - 1) })
}
}
I am using swift, xcode 8.3.3, and XCTest. I am trying to wait for an element to exist on the screen using XCTKVOExpectation. It is always returning the result of timedout (2) even though the element exists.
Here is my code:
func waitForElementToAppear(element: XCUIElement) -> Bool {
let expectation = XCTKVOExpectation(keyPath: "exists", object: element,
expectedValue: true)
let result = XCTWaiter().wait(for: [expectation], timeout: 10)
print(element.exists)
print(result.rawValue)
return result == .completed
}
When I print element.exists, it prints true. However the result.rawValue is 2 (.timedout) Increasing the timeout value did not resolve this either.
I am able to successfully use XCTNSPredicateExpectation:
let myPredicate = NSPredicate(format: "exists == true")
let myExpectation = XCTNSPredicateExpectation(predicate: myPredicate,
object: element)
let result = XCTWaiter().wait(for: [myExpectation], timeout: 10)
return result == .completed
Wondering why XCTKVOExpectation doesn't work though?
Not really an answer, but I've been seeing this in Xcode 9.3.1 as well. The exact same predicate combination works well elsewhere in many places but one in particular almost always times out.
I've worked around it for now by checking whether the object exists before issuing the wait, and then also "result == XCTWaiterResultCompleted || object.exists" (Objective C).
I'd love to know if this is a bug or am I doing something wrong?
I was having the same problem with my own code. I was able to get the KVO change to trigger using didChangeValue(forKey: "keyValuePath").
Since XCTKVOExpectation accepts a String for the key path instead of a KeyPath, paired with needing to call didChangeValue(forKey: "keyValuePath"), it seems XCTKVOExpectation is not meant to work with Swift and is more of a legacy objc object. I just don't think this is going to work in Swift
I have built a "metal detector" kind of app that runs the "calculateDistance" function every 2 seconds, which calculates the distance in meters between the user location and a set marker, sets that to var globalDistance. Depending on if that distance, for simplicity, is >=10meters or <10meters, I am playing a scheduled timer that calls the "audioplayer" function, which plays a "beep" sound every 2 seconds (if distance>=10m) or every 0.5seconds (if distance <10m).
Problem is, the timers never invalidate as I instruct them to. So if I move from <10m to >10m with my device, the 0.5sec beeping continues. I do audioTimer.invalidate() to stop the timer running from previous iteration.
Any idea what I am doing wrong with my code? Many thanks
func calculateDistance {
//here there is code that successfully calculates distance, every 2 seconds
var timerSeconds = 0.0
var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad
timerSeconds = 2
}
if globalDistance >= 0 && globalDistance <= 10 {
timerSeconds = 0.5
}
audioTimer.invalidate()
audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
audioTimer.fire()
}
func audioPlayer(){
AudioServicesPlaySystemSound(1104)
}
The basic idea is to make sure there is no code path by which a Timer is started without stopping any prior one. You current code has a couple of paths by which an existing timer is not invalidated before starting the next one.
Furthermore, I would suggest that you only invalidate the old Timer if the new beep frequency is different than the old beep frequency. (Why invalidate the 2 second repeating beeping and start another timer if the old 2 second timer will do the job fine?)
So, this means that you will:
pull both the Timer and the TimeInterval variables out of the function;
only do the "new timer" process if it decides that the beep interval has changed; and
make sure to always invalidate the old timer before creating a new one.
For example:
private var audioTimer: Timer?
private var beepInterval: TimeInterval?
private func updateBeepIntervalIfNeeded() {
// here there is code that successfully calculates distance, called with whatever frequency you want
let newBeepInterval: TimeInterval
if globalDistance > 10 {
newBeepInterval = 2
} else if globalDistance >= 0 {
newBeepInterval = 0.5
} else {
fatalError("less than 0?!") // I'm inferring from your code that this cannot happen, but by using `let` above, Swift warned me that we had a path of execution we hadn't previously considered
}
if beepInterval != newBeepInterval {
beepInterval = newBeepInterval
audioTimer?.invalidate()
audioTimer = Timer.scheduledTimer(timeInterval: beepInterval!, target: self, selector: #selector(beep(_:)), userInfo: nil, repeats: true)
audioTimer!.fire()
}
}
#objc func beep(_ timer: Timer) {
// perform beep here
}
The problem
There's several issues at hand here.
Firstly, I'd like to emphasis the difference between references and instances. When you call call an initializer, the system allocates a piece of memory for a new object, and gives you a reference to that memory, which is stored in whatever variable you assign it to. You can assign this reference to other variables, which will make copies of the reference. Each of these variable references the same original object. This object will continue to exist in memory until no more variables reference it.
In your case, you're not directly calling an initializer, but you're calling a static method which serves a similar purpose. A new object is allocated on your behalf, and you're given a reference, which you then assign to audioTimer. There's a catch to this, however. When you call Timer.scheduledTimer(timeInterval:target:selector:userInfo:repeats:), the newly constructed timer is scheduled on the current run loop for you. The run loop is what's in charge of firing your timer at the right time. The consequence of this is that now the runloop is referencing your timer, preventing the timer object from being destroyed. Unless you invalidate your timer to unregister it from its runloop, the timer will continue to exist and fire forever, even after you delete your deference to it.
Now let's take a look at your code, with some explanation as to what's going on:
func calculateDistance {
//here there is code that successfully calculates distance, every 2 seconds
var timerSeconds = 0.0
// 1) Initialize timer #1
var audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
if globalDistance > 10 { // globalDistance is where i set the distance every 2 seconds, with a timer fired on ViewDidLoad
timerSeconds = 2
}
if globalDistance >= 0 && globalDistance <= 10 {
timerSeconds = 0.5
}
// 2) Invalidate timer #1 (timer #1 is useless)
audioTimer.invalidate()
// 3) Initialize timer #1
audioTimer = Timer.scheduledTimer(timeInterval: (timerSeconds), target: self, selector: #selector(googleMaps.audioPlayer), userInfo: nil, repeats: true)
// 4) Fire timer #2 immediately
audioTimer.fire()
} // At the end of this method body:
// - Timer #2 was never invalidated
// - audioTimer no longer references Timer #2, but:
// - Timer #2's runloop still references it, keeping it alive
// - Timer #2 is leaked
// ... and will continue firing forever.
func audioPlayer(){
AudioServicesPlaySystemSound(1104)
}
We can see that a Timer is made in section one, which should fire off in timerSeconds seconds, 0. At section 2, that timer is invalidated. Even though the Timer was to fire off in 0 seconds, it is almost certain that its run loop hasn't gotten a chance to fire it yet. Thus, this time is created, never fires, and then invalidated. There's no reason for it to exist at all there.
Then, in section 3, Timer #2 is created and scheduled. it is manually fired at section 4, and then it's permanently leaked.
The solution
You need an instance variable that holds reference to the timer. Without this, you have no way of invalidating the timer that has been already scheduled.
Secondly, you need to invalidate the timer at the appropriate time.
I suggest you take a look at Rob's answer for an example.
Youre creating a new, infinitely repeating, timer once, invalidate it immediately (why?), and then create another (why?), which is leaked forever.
You are creating a new timer,invalidating then creating a timer again.
You could try creating the timer,and when calling the audioPlayer function,checking for which sound to play depending on the value of the timerSeconds variable.
I am able to play a sound in swift with NSSound:
var path ="/path/to/file.mp3"
mysound = NSSound(contentsOfFile: path, byReference: false)
mysound!.play() // works!
if (mysound!playing)
{
mysound!.pause() //works, sort of
}
mysound!playing // is still true.
How do I check to see if it's paused?
The docs show that pause() is supposed to return false if playback already paused, but this doesn't seem to be the case, or I am doing something wrong
I'd like to be able to do this:
var path ="/path/to/file.mp3"
mysound = NSSound(contentsOfFile: path, byReference: false)
if (mysound!playing)
{
var state = mysound!.pause()
if(state == false)
{
mysound!.resume() //never gets executed - why?
}
} else {
mysound!.play()
}
I could use some help figuring this out! thanks!
The sound will only be paused if you called pause() and it returns true. The documentation doesn't qualify, but a paused NSSound might technically still return true for playing. This would make sense if playing is considered to be "not stopped".
Since you will know if you've called pause(), you can just store another Bool isPaused, and use that.
In your second example, you're not calling play(), so playing will always return false.
I have a function with one parameter. This parameter has a default value. (true)
func disableButton(animated: Bool = true) {
if (animated) {
...
} else {
...
}
}
So i can call the function as:
disableButton(animated: true)
or:
disableButton()
and they give me the same result.
Now I have an NSTimer running a selector on completion like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton"), userInfo: nil, repeats: false)
It will send the disableButton() message on completion. However on completion it causes the app to crash.
If i do:
func disableButton() {
disableButton(animated: true)
}
Then the timer successfully sends that message and the app does not crash.
Of course this is really ugly and kind of limits the great Swift feature of default parameters.
Is this a bug (that I should report) or am I doing it wrong?
Thanks for any help!
There's a difference between -disableButton and -disableButton: selectors. But you can't just use your desired selector like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton:"), userInfo: nil, repeats: false)
because NSTimer here assumes selector with no arguments or selector with single argument (which will be used to pass firing NSTimer instance).
I believe, you should migrate from using selectors, as you want to use all that great features Swift delivers, as far as selectors is outdated pattern.
Just a note: it could work if you use "disableButton:" selector, maybe NSTimer's pointer will be interpreted as true and maybe not, not sure what will happen (maybe it could crash due to Swift strong typing stuff). But depending on that behaviour is a bad practice which could lead to bugs which are extremely hard to exterminate.