Asynchronous task that is canceled when screen is touched in Swift - ios

I am developing an application which has 5 animations on each page. each animation is launched when you click on them. to assist the user in 5 seconds if not pressed any image, aid is released. this is repeated for each animation, if you click on the animation 1, when the animation ends, it throws back the counter 5 seconds, but in those 5 seconds is pressed in any animation, the counter should be canceled. as I can manage it? until now did with dispatch_after but not how to cancel

If you want your operations to be cancelable, one easy approach is to use an NSBlockOperation.
let operation = NSBlockOperation()
operation.addExecutionBlock { () -> Void in
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(5 * NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue(), { () -> Void in
if operation.cancelled {
return
} else {
// perform your animation
}
})
}
To start your operation:
operation.start()
If you want to cancel your operation:
operation.cancel()
If your operation has many stages, you should periodically check operation.cancelled - calling cancel() does nothing itself beside set that to true.
For more complex work, you can chain multiple operations together in a dependency and put them in an NSOperationQueue.

Related

Is it normal that CPU usage exceeds 100% using dispatch async in Xcode 7

I'm a beginner in swift 2, and I'm trying to make my program blocks while showing only a progress spinner until some operation finishes, I made that code snippet in a button with the action "touch up inside", my problem is that while debugging,Xcode 7 CPU usage jumps to 190 % once I tap my button and keeps high until the flag changes its value, Is it normal that CPU usage jumps like that?, also Is it a good practice to use the following snippet or shud i use sleep or some other mechanism inside my infinite loop?
let queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(self.queue2) { () -> Void in
while(flag == true)
{
//wait until flag sets to false from previous func
}
self.dispatch_main({
//continue after the flag became false
})
This is a very economical completion handler
func test(completion:() -> ())
{
// do hard work
completion()
}
let queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue2) {
test() {
print("completed")
}
}
or with additional dispatch to the main queue to update the UI
let queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue2) {
test() {
print("completed")
dispatch_async(dispatch_get_main_queue()) {
// update UI
}
}
}
This is totally wrong approach as you are using while loop for waiting. You should use Completion Handler to achieve this kind of stuff.
Completion handlers are callbacks that allow a client to perform some action when a framework method or function completes its task. Often the client uses a completion handler to free state or update the user interface. Several framework methods let you implement completion handlers as blocks (instead of, say, delegation methods or notification handlers).
Refer Apple documentation for more details.
I suppose you have a sort of class which manages these "some operation finishes".
When you finish your operations you can comunicate by completion handler or delegation. In the meanwhile you can disable the user interaction of your UI until the end of these operations.
If you provide more informations about your background operations I can add some snippets of code.

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

Performing an unknown amount of animations sequentially

I am creating a game where the user can move a SKShapeNode around. Now, I am trying to write a utility function that will perform back-to-back animations sequentially.
Description of what I'm Trying
I first dispatch_async to a serial thread. This thread then calls a dispatch_sync on the main thread to perform an animation. Now, to make the animations run sequentially, I would like to block the GlobalSerialAnimationQueue until the animation is completed on the main thread. By doing this, I (theoretically) would be able to run animations sequentially. My code is pasted below for more description
func moveToCurrentPosition() {
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: 1.0)
dispatch_async(GlobalSerialAnimateQueue) {
//this just creates an action to move to a point
dispatch_sync(GlobalMainQueue, {
self.userNode!.runAction(action) {
//inside the completion block now want to continue
//WOULD WANT TO TRIGGER THREAD TO CONTINUE HERE
}
})
//WOULD LIKE TO PAUSE HERE, THIS BLOCK FINISHING ONLY WHEN THE ANIMATION IS COMPLETE
}
}
So, my question is, how would I write a function that can take in requests for animations, and then perform them sequentially? What grand-central-dispatch tools should I use, or should I be trying a completely different approach?
I figured out how to do this using a grand-central-dispatch semaphore. My updated code is here.
func moveToCurrentPosition() {
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: Animation.USER_MOVE_DURATION)
dispatch_async(GlobalSerialAnimateQueue) {
let semaphore = dispatch_semaphore_create(0)
dispatch_sync(GlobalMainQueue, {
self.userNode!.runAction(action) {
//signal done
dispatch_semaphore_signal(semaphore)
}
})
//wait here...
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
}
SpriteKit provides a much simpler and intuitive API for running actions in sequence. Have a look at the documentation here.
You can simply perform actions as a sequence of events, with blocks in between or as completion:
let action = SKAction.moveTo(self.getPositionForCurrRowCol(), duration: Animation.USER_MOVE_DURATION)
let otherAction = SKAction.runBlock({
//Perform completion here.
})
self.userNode!.runAction(SKAction.sequence([action, otherAction]))

How to wait first dispatch to finish execution

I have 2 dispatch_async() like this :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
/* Code here */
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
/* Code here */
}
I want the second dispatch to wait until the first one finish his execution. how i can do that ?
Thank's in advance
I'll give a few solutions, in the order of increasing complexity:
1
The simplest way is to include both code blocks in the same async calls:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// code block 1
// code block 2
}
2
If you don't know precisely when they will run, for example, code block 1 is triggered when user presses a button and code block 2 is run when user presses another button, use a serial queue:
let serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL)
dispatch_async(serialQueue) {
// code block 1
}
dispatch_async(serialQueue) {
// code block 2
}
3
If your code blocks run asynchronously, like first making a webservice call to authenticate, then making a second call to get the user's profile, you have to implement waiting:
let groupID = dispatch_group_create()
let task1 = session.dataTaskWithRequest(request1) { data, response, error in
// handle the response...
// Tell Grand Central Dispatch that the request is done
dispatch_group_leave(groupID)
}
let task2 = session.dataTaskWithRequest(request2) { data, response, error in
// handle the response...
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
dispatch_group_enter(groupID) // Tell GCD task1 is starting
task1.resume()
dispatch_group_wait(groupID, DISPATCH_TIME_FOREVER) // Wait until task1 is done
task2.resume()
}
4
For anything more complicated, I strongly suggest you learn NSOpereationQueue. There's a WWDC session on it

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