How to wait for all group actions? - ios

I can't wait for group of SKAction because completion handler executes immediately. Here is basic example:
let a1 = SKAction.runAction(SKAction.fadeAlphaTo(0.5, duration: duration), onChildWithName: child1.name!)
let a2 = SKAction.runAction(SKAction.fadeAlphaTo(1, duration: duration), onChildWithName: child2.name!)
runAction(SKAction.group([a1, a2]), completion: { () -> Void in
// do something
})
Nodes child1 and child2 are children of scene. When I run app on iPhone 5s (iOS 8.4) simulator I see that completion block doesn't wait while group actions finished and starts immediately.
So my two questions:
Is it s bug? If no, where is in SKAction documentation I can find explanation of this behaviour.
How to do this right? I know I can use counter and start each action a1 and a2 with completion block and just check when counter became 0 but it looks like a boilerplate code for me.

According to Apple's documentation, SKAction.runAction returns an action of instantaneous duration (that is 0 duration).
Thus in your code both SKActions, a1 and a2, have instantaneous duration. This is why the completion block is called immediately.
From Apple Documentation
SKAction.runAction : This action has an instantaneous duration,
although the action executed on the child may have a duration of its
own.
https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKAction_Ref/#//apple_ref/occ/clm/SKAction/runAction:onChildWithName:
One way to set the completion handler would be to set it to one of the child nodes, since both actions have the same duration.
let a1 = SKAction.fadeAlphaTo(0.5, duration: 1);
let a2 = SKAction.fadeAlphaTo(1, duration: 1);
child1.runAction(a1)
child2.runAction(a2) { () -> Void in
//completion code
}

Related

Why is my timer's tolerance always halved?

In my app, I'm scheduling a timer like this:
let period = TimeInterval(10)
let timer = Timer.scheduledTimer(withTimeInterval: period, repeats: true, block: { [weak self] (_) in
// Some repeating code here
})
timer.tolerance = period
Essentially, I want a timer to fire once in every consecutive, repeating 10 second period, but it doesn't matter when the timer fires in each individual period. However, if I set a breakpoint in the debugger for immediately after this code runs. I can see that my timer's timeInterval is set to 10 seconds, but the timer's tolerance is set to 5. I've played around with various values for period, but no matter the case, it seems that my timer's tolerance will always be half of its timeInterval. Why is that? Will this still produce the functionality I intend? If not, how can I prevent this from happening?

Order in which code is executed with UIView.animate

I have an issue with my code which exemplified by a simplified version below. I expect for "2" to be printed before "1". However, that is not the case.
Does this have something to do with the fact that the code doesn't actually run from top to bottom?
if moveConclusion.patternDetected == true {
for i in 0...8 {
if pressedArray[i] {
self.panlButtons[i].backgroundColor = UIColor.clear
UIView.animate(withDuration: 0.1, animations:{
self.panlButtons[i].backgroundColor = self.correctColour
//self.panlButtons[i].transform = CGAffineTransform(rotationAngle: CGFloat.pi / -12)
}, completion: { finished in
UIView.animate(withDuration: 0.1, animations:{
self.panlButtons[i].backgroundColor = UIColor.clear
// self.panlButtons[i].transform = CGAffineTransform(rotationAngle: CGFloat.pi / 12)
print("2")
})
})
}
}
print("1")
}
Output:
1
2
2
2
...
Calling UIView.animate orders the animation; it does not perform it. The animation engine will perform it — later. You are handing to the animation engine two blocks to be executed later, meaning after all your code comes to an end (to be technical, it's when the next screen refresh frame comes along and the current CATransaction is committed):
The animations block will run after all your code has finished and it's time for the animation to start.
The completion block will run after the animation ends (that's what "completion" means).
UIView.animate runs on the main thread and is asynchronous. So as soon as that code is called it dispatches it onto the NEXT runloop, (which is after the this function terminates, which means after the entire for loop).
In other words, the UIView.animate call essentially just queues it for the next run loop. And the print("1") statement still occurs on the current run loop.
Then the animation block is run, and once the animation block finishes, the completion block runs. So the print for the 2 comes WAAAAAY later in terms of CPU processing, and is definitely the expected behavior.
This is because print("2") executes approximately 0.1-0.2 seconds after this code starts, but print("1") happens after the for loop executes.
The animations do not execute synchronously with the for loop. The print("2") will happen during the second animation, but by that time, the for loop has completed and print("1") has executed.

Animating Image in Watch App doesn't work (Swift)

I have a problem with my animated image.
In my page, I have a label in the center, initialised with a text "Start dictation" and an Image at the bottom initialised without image
There is my code :
func dictation() {
let seconds = 1.0
let delay = seconds * Double(NSEC_PER_SEC) // nanoseconds per seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.label.setText("")
self.myImage.setImageNamed("frame-")
self.myImage.startAnimatingWithImagesInRange(NSMakeRange(0, 15), duration: 0.5, repeatCount: 0)
})
presentTextInputControllerWithSuggestions([], allowedInputMode: .Plain, completion: { (selectedAnswers) -> Void in
if ((selectedAnswers != nil) && (selectedAnswers!.count>0) ){
if selectedAnswers![0] is String {
self.myImage.stopAnimating()
self.myImage.setImageNamed("")
self.label.setText((selectedAnswers![0] as! String))
}
}
})
}
When my dictation is finished, there is a time before the displaying of my text. So, I tried to add animation to see that it's in progress.
Here, I want to start my dictation, start in background my animation and clear my text. And, when my speech is ready to be display, I want to stop and clear the animation and print my text.
My problem is : sometimes, when I come back on my page after dictation, I found my first text "Start dictation" and not my animation.
I tried with debug mode and I added breakpoints and logs in all my code. All is executed in the good order but the result is really random..
I saw also that my animation doesn't stop when I use stopAnimating() and doesn't clear when I use setImageNamed("").
Could you help me ?
When I started animation, I wasn't on the main page so, the code was executed but "self" was not my main page.
To solve this, I just call my animation in the willActivate function when I come back on the main page

How to update variable based on an external event in Swift?

I am using a Particle Core to get the temperature from my room. The temperature is accessed through the cloud, which is being constantly updated in a variable. This is how I access the variable and display it:
func updateTemp(){
let seconds = 3.0
let delay = seconds * Double(NSEC_PER_SEC) // nanoseconds per seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.myPhoton?.getVariable("tempF", completion: { (result:AnyObject!, error:NSError!) -> Void in
if let _ = error {
print("Failed reading temperature from device")
}
else {
if let larry = result as? Int {
self.temp.text="\(larry)˚"
self.truth++ //Once a value has been found, update the count.
}
}
})
})
}
override func viewDidLoad() {
sparkStart()
}
override func viewDidLayoutSubviews() {
updateTemp()
NSTimer.scheduledTimerWithTimeInterval(100.0, target: self, selector: "updateTemp", userInfo: nil, repeats: true) //Gaurantees that the app is updated every 100 seconds. That way we have a fresh temperature often.
//Stop the spinning once a value has been found
if truth == 1{
activity.stopAnimating()
activity.removeFromSuperview()
}
}
Since this is my Particle Core detecting the temperature from environment, the temperature variable is constantly changing. However, when I use NSTimer, the code does not get updated in the time specified. Instead, it begins by updating based on the specified time, but then the time starts decreases exponentially and the variable is updated every 0.001 seconds or so. Any thoughts?
Im assuming what we see is not the full code. In your viewDidLayoutSubviews function, you call updateTemp twice. Once explicitly and once via timer callback.
Your updateTemp function schedules the network call in the main run loop, that's where the timer is also running. The dispatch_after function queues the execution of the readout updates one after the other. I am now assuming, that something in your display code causes repeated triggers of viewDidLayoutSubviews, each of which schedules two new updates etc. Even if the assumption is false (there are a couple of other possibilities due to network code being slow and the timer also running in the main run loop), I am guessing if you drop the explicit call to updateTemp you'll lose the "exponential" and should be fine.
In general, as the web call is largely asynchronous, you could just use the timer and call your sensor directly or if you feel GCD has an important performance advantage switch to dispatch_async and apply for the next available queue with each call via calling dispatch_get_global_queue

apple watch - slow image animation first time

I'm building a small app for apple watch. I have a Group and a Label inside of it. What I'm trying to do is:
animate background image of the group
fade in label after image animation ends
My code looks essentially like this:
group.setBackgroundImageNamed("show_back-");
group.startAnimatingWithImagesInRange(NSMakeRange(0, 39), duration: 1.5, repeatCount: 1);
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) { () -> Void in
self.animateWithDuration(1) { () -> Void in
self.label.setAlpha(1)
};
};
The problem is that the first time this sequence is triggered, the image animation seems to run slower than 1.5 seconds, because the label starts fading in earlier than the images stop changing. If this is triggered again while the app is running, everything works as expected. I guess it has something to do with images preloading or something.
How can I make it work consistently? I couldn't find any sort of callback on image sequence animation end to subscribe to.
EDIT
Another problem I've noticed: I have another case when bg is animated from a dispatch_after block, and when I leave the app by tapping the crown and return by double-tapping it, either the dispatch_after block is not triggered, or the background animation is not rendered correctly the first time it is invoked (I think the second, because I tried adding a breakpoint into the dispatch block and it triggered every time I tested).
I'm running watchOS2, so maybe it is related to the beta state the OS is currently in?
I ran into the same issue as you.
This happens because on the first time you try it, the watch takes time to load the images. Also apple doesn't give us any 'pre load' method, so I came up with a little work around it:
When my controller will be displayed:
func willActivate()
I play the animation sequence once in a background tread, this way when my user clicks on it the images are already loaded.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { [weak self] in
if let uSelf = self {
uSelf.statusAnimationImage.setImageNamed("my image name")
uSelf.statusAnimationImage.startAnimatingWithImagesInRange(NSMakeRange(0, 359), duration: 0.5, repeatCount: 1)
}
}
That was the best way I found to solve this problem and it works for me.
try doing
group.setBackgroundImageNamed("show_back-");
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1.5 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) { () -> Void in
self.animateWithDuration(1) { () -> Void in
group.startAnimatingWithImagesInRange(NSMakeRange(0, 39), duration: 1.5, repeatCount: 1);
self.label.setAlpha(1)
};
};
I'm not exactly sure what you're doing but also try doing animateWithDuration(0) or (1.5)

Resources