Stop dispatch_after
Asked Answered
G

5

22

I use an animation for specify a tip to help the interaction with delay using these:

 let delay = 1.8 * Double(NSEC_PER_SEC)
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
    dispatch_after(time, dispatch_get_main_queue()) {
        //call the method which have the steps after delay.

        self.rain.alpha = 0

        UIView.animateWithDuration(5, animations: {

            self.rain.alpha = 1

        })

        self.tip.startAnimating()

    }

But, I need to stop this delay process if, before animation start, user touch the screen.

Glori answered 5/2, 2015 at 23:53 Comment(1)
Possible duplicate of Cancel a timed event in Swift?Perigon
G
56

iOS 8 and OS X Yosemite introduced dispatch_block_cancel that allow you to cancel them before they start executing

You declare one variable in class as follows:

var block: dispatch_block_t?

Init block variable and provide it in dispatch_after:

block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
  print("I executed")
}
let time: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(5 * NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue(), block!)

After that you can cancel it as follows:

dispatch_block_cancel(block!)
Ghent answered 15/6, 2016 at 8:12 Comment(5)
You really should be upvoted more; not enough people know about this…Tedious
on Swift 4 code it say: 'dispatch_block_t' is unavailable in SwiftStoriette
This is a good answer but needs to be updated for Swift. Use the following APIS instead: var block = DispatchWorkItem { //do stuff } and then DispatchQueue.global().async(execute: block) and finally block.cancel()Keil
Once a dispatch_block_cancel() has been called, subsequent calls to dispatch_after() seem to get ignored. Is there a way to call dispatch_after() again normally? (I am using objective-C not swift)Glyptics
Learned something new today :) Thanks :)Azikiwe
B
32

Swift 3.0 Example DispatchQueue cancel or stop

var dispatchQueue: DispatchQueue?
var dispatchWorkItem: DispatchWorkItem?

func someOnClickButtonStart() {
    self.dispatchQueue = DispatchQueue.global(qos: .background) // create queue
    self.dispatchWorkItem = DispatchWorkItem { // create work item
        // async code goes here
    }
    if self.dispatchWorkItem != nil {
        self.dispatchQueue?.asyncAfter(
            deadline: .now() + .seconds(1),
            execute: self.dispatchWorkItem!
        ) // schedule work item
    }
}

func someOnClickButtonCancel() {
   if let dispatchWorkItem = self.dispatchWorkItem {
        dispatchWorkItem.cancel() // cancel work item
    }
}
Beene answered 16/10, 2016 at 13:47 Comment(2)
I am going to give you the upvote, because this worked eventually. But this code needs some work.Lydalyddite
This solution works well. The principal of using DispatchWorkItem is the right solution in Swift.Limnology
C
8

Here's a general solution I wrote to cancel a dispatch_after in Swift:

typealias cancellable_closure = (() -> ())?

func dispatch_after(#seconds:Double, queue: dispatch_queue_t = dispatch_get_main_queue(), closure:()->()) -> cancellable_closure {
    var cancelled = false
    let cancel_closure: cancellable_closure = {
        cancelled = true
    }

    dispatch_after(
        dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))), queue, {
            if !cancelled {
                closure()
            }
        }
    )

    return cancel_closure
}

func cancel_dispatch_after(cancel_closure: cancellable_closure) {
    cancel_closure?()
}

Usage:

let doSomethingLater = dispatch_after(seconds: 3.0) {
    something()
}
....
if shouldCancelForSomeReason {
    cancel_dispatch_after(doSomethingLater)
}

By default it runs on the main queue, but you can pass in a parameter for it to run on another queue:

let doSomethingLater = dispatch_after(seconds: 3.0, queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
    something()
}
Creamer answered 11/6, 2015 at 14:3 Comment(0)
P
5

Just sharing, in Swift 4.x, I do this:

var block: DispatchWorkItem?

self.block = DispatchWorkItem { self.go(self) }

// execute task in 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: self.block!)

and then to cancel the block, self.block?.cancel()

Try this sample project:

import UIKit

class ViewController: UIViewController {

    var block: DispatchWorkItem?

    @IBAction func go(_ sender: Any) {
        self.block?.cancel()
        let vc2 = VC2()
        self.navigationController?.pushViewController(vc2, animated: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .white
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        self.block = DispatchWorkItem { self.go(self) }

        // execute task in 2 seconds
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: self.block!)
    }    
}


class VC2: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .green
    }
}
Purdah answered 17/9, 2018 at 4:10 Comment(0)
Y
3

You can create a boolean variable shouldCancelAnimation and test it inside the dispatch_after block to prevent the execution of your animation.

var shouldCancelAnimation = false // property of class

func runAnimation()
{
    let delay = 1.8 * Double(NSEC_PER_SEC)
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
    dispatch_after(time, dispatch_get_main_queue()) {

        if !self.shouldCancelAnimation
        {
            self.rain.alpha = 0
            UIView.animateWithDuration(5, animations: {
                self.rain.alpha = 1
            })

            self.tip.startAnimating()
        }
        self.shouldCancelAnimation = false
    }
}

func viewWasTouched() // This could be touches began or taprecognizer event
{
    shouldCancelAnimation = true
}
Yerxa answered 6/2, 2015 at 3:14 Comment(1)
this would not work if you call runAnimation again before the delay time.Edisonedit

© 2022 - 2024 — McMap. All rights reserved.