21

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.

rakeshbs
  • 24,392
  • 7
  • 73
  • 63
PlugInBoy
  • 979
  • 3
  • 13
  • 25
  • Possible duplicate of [Cancel a timed event in Swift?](http://stackoverflow.com/questions/28359768/cancel-a-timed-event-in-swift) – David Lawson Sep 25 '16 at 07:48

5 Answers5

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!)
Quang Tran
  • 1,309
  • 13
  • 14
  • 6
    You really should be upvoted more; not enough people know about this… – Frizlab Jul 18 '16 at 10:19
  • on Swift 4 code it say: 'dispatch_block_t' is unavailable in Swift – Vitaliy A Dec 12 '17 at 16:00
  • 1
    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()` – William henderson Dec 29 '17 at 00:07
  • 1
    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) – user13267 Dec 19 '18 at 08:55
  • Learned something new today :) Thanks :) – Harish Pathak Apr 06 '21 at 11:01
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
    }
}
Regexident
  • 29,441
  • 10
  • 93
  • 100
amiron
  • 721
  • 9
  • 11
  • I am going to give you the upvote, because this worked eventually. But this code needs some work. – dfmuir Mar 30 '17 at 23:05
  • This solution works well. The principal of using `DispatchWorkItem` is the right solution in Swift. – Jessedc Aug 17 '18 at 04:51
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()
}
John Wallace
  • 161
  • 1
  • 2
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
    }
}
Glenn Posadas
  • 12,555
  • 6
  • 54
  • 95
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
}
rakeshbs
  • 24,392
  • 7
  • 73
  • 63