I am working on an Xcode playground in Swift and I would like for a selection of sprites to perform some actions in a set order, one after the other. However they appear to be happening all at once, despite my use of the completion handler.
Any while loops I try (using a boolean to see if the action is completed or not - set to true in the completion handler) result in an infinite loop.
Here is a code I've tried:
centreOfRotation.run(SKAction.rotate(byAngle: CGFloat(degreesToRadians(angleInDegrees)), duration: 5), completion: {
finished = true
print("finished")
})
But this code doesn't wait for completion before going to the next line.
Does anyone know how to stop this?
Thanks.
Related
Has anyone managed to wait for a specific, single, XCTestExpectation with completion handler (XCWaitCompletionHandler or equivalent) similar to that of (waitForExpectations:timeout:handler)[https://developer.apple.com/documentation/xctest/xctestcase/1500748-waitforexpectations].
I want to wait for just one specific expectation, but I would like to know if it timed out. The XCWaitCompletionHandler gives me that information. But there are no properties on the expectation itself flagging whether it timed out or not.
(yes this is a rather advanced use case...)
Create an XCTWaiter and use wait(for:timeout:), which returns a XCTWaiter.Result, which you can then assert is/is not .timedOut if you wish, or otherwise act on the result. By using XCTWaiter, you can have an expectation time out without the test automatically failing.
let result = XCTWaiter().wait(for: [expectation], timeout: 5)
if result == .timedOut { ... }
I'm struggling with asynchronous code in Swift. I have to disable 2 buttons if there is no internet connection. I have 2 DispatchQueue.main.async calls in 2 different functions. 1 of them works, but the other one doesn't disable the button (the good thing is that you can click on the button and nothing happens). I am testing my code on my iPhone because the simulator does not run the SDK I am using properly.
All these functions look like this (this is the one at the end that does not work):
public func disableButton2(_ check: Bool) {
DispatchQueue.main.async{
self.button2.isEnabled = check
}
I have used this and it was working fine
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { [weak self] in
guard let self = self else {
return
}
try this you will get the result.
A kind of more accurate approach, in order to explain "why asyncAfter works instead of just async" would be that DispatchQueue.main.asyncAfter would capture the thread in order to make the UI change to happend.
But most of the times could be because your are trying to update a view element within another view, say UICollectionView inside a UIViewController. In order to solve this you should call async method inside the method that makes this UI update
In my code I have a sequence in which I am telling swift to wait for a sequence to complete before i move on to another function however it seems to skip this and just move on
let earthSequence = SCNAction.sequence([SCNAction.wait(duration: 10),rollInGroup,rollOutGroup,earthNormalRotation])
defaultEarthNode.runAction(earthSequence)
nextFunction()
the result is simply moving on to the next function without waiting 10 seconds
That's because nextFunction() is called immediately after you start the action, it doesn't wait for it to be completed. runAction has a completionHandler you can use to know when the sequence has finished.
defaultEarthNode.runAction(earthSequence, completionHandler: {
nextFunction()
})
I have an idle player animation and I want to do a smooth transition between some animations. The idle animation is the default one and from that transition I want to be able to switch to another state(let's say fight) and then back to idle. The code for the idle character animation is currently like :
self.addChild(playerAnimation)
playerAnimation.runAction(SKAction.repeatActionForever(
SKAction.animateWithTextures(playerAnimationManager.idleManAnimation.textureArray, timePerFrame: 0.1)))
Now, this is scheduled to go on forever for now, but I would need to intercept it and add a new animation on top of that (which is the same character, in a new state). I was thinking that I should stop the idle animation, switch to the new one and then back to idle when finished but I am not convinced that this is the best way of chaining animations and I haven't really found a good resource explaining how to go about it.
Any suggestions ? Thanks !
Depending on how short your texture array is, you might be able to do this.
I will try to explain without code seeing how I use objective C and you use Swift
First make a property or variable that can be called by any subroutine in this class file. It should be Boolean and should be set to NO. You could call it idleFlag.
Next make a method that changes the animation to fight mode. This change would be by removing the idle animation and replacing it with the fight animation. This method also set's idleFlag to NO. Let's call the method "beginFightAnim"
And last, in your repeatActionForEver idle animation, right after your animateWithTextures animation, add a runBlock animation. In this block define a static variable (one that will be remembered in the calling of the block over and over) and increment it by +1, add an "if statement" that looks something like this -> if (my_static_var == number_of_frames_in_texture_animations && idleFlag). And in the "if statement" set the static variable to 0 and call "beginFightAnim"
After this all you have to do to change the animation is set idleFlag to YES.
Hope it works!
If you have any problems, please comment below.
I want to do a series of examples to make it easier to understand the matter. Suppose you have your node called playerAnimation, a typical atlasc with a plist where you have
player-idle1.png
player-idle2.png
player-idle3.png
...
If you want to intercept in real-time your animation to know what frame is running in that moment you can do:
override func update(currentTime: CFTimeInterval) {
if String(playerAnimation.texture).rangeOfString("player-idle") != nil {
print(String(playerAnimation.texture))
}
}
At this point, after you have assigned a "key" to your action (withKey:"idleAnimation") you could stop your specific idle action when you preefer to start the other next animation.
Another good thing is to build a structure to know everytime your player status and modify this variable each time you launch a new action or animation:
enum PlayerStatus: Int {
case Idle = 1
case Fight = 2
case Run = 3
case Jump = 4
case Die = 5
...
}
var currentStatus: PlayerStatus!
I try to create UI test on the application where sometimes AD popup appears and if so I need to click on close button. I tried to use:
waitForExpectationsWithTimeout(timeout: NSTimeInterval, handler: XCWaitCompletionHandler?)
with
let existsPredicate = NSPredicate(format: "exists == true")
But the problem is that the method treats test as failed which is not acceptable for my case.
I tried to use .exists after custom sleep but from my observations it just always returns false even though element does appear on the screen.
Also plaid around with .hittable and it seems to be true even though the element didn't appear on the screen yet.
From my understanding it would be great if Apple gives control of whether waitForExpectationsWithTimeout treats test as fail or not, but I was not able to find anything.
Any advice would be greatly appreciated!
i can suggest you not to use handler XCWaitCompletionHandler in this case.
expectationForPredicate(predicate, evaluatedWithObject: evaluatedWithObject, handler: nil)
waitForExpectationsWithTimeout(TIMEOUT, handler: nil)
if (evaluatedWithObject.exist) {
//dosmth
}
so it is soft assert that smth appears. If element not found it just will wait time and go further
Unfortunately the only solution I've found is to do it manually: use a while loop and check for object.exists for a waiting period to prevent the expectation wait failure.