Swift: How to cancel a delayed pushViewController?

120 views Asked by At

I have this situation where I can push two different view controllers from a given view controller, the first one will be pushed immediately, the second one after an API call, so to mimic it I have written this:

class TestViewController: UIViewController {
    @IBAction func pushFirstVC() {
        DispatchQueue.main.async { [weak self] in
            weakSelf?.navigationController?.pushViewController(FirstViewController(), animated: true)
        }
    }
    
    @IBAction func pushSecondVC() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
            self?.navigationController?.pushViewController(SecondViewController(), animated: true)
        }
    }
}

Now let's say the user first triggers the pushSecondVC() event, then during the 5 next five seconds he triggers the pushFirstVC() - and then I would like to definitely cancel the initial pushSecondVC() in order not to push a SecondViewController instance, even if the user pops back to TestViewController in this 5 seconds delay.

I have tried several things, like adding a canPush flag that becomes true in viewDidAppear and false in viewDidDisappear. But this doesn't prevent the push view controller from happening if the user came back in TestViewController in the 5 seconds delay. So what I would like to do would be to specifically cancel the expected task, but I have no idea how to do so.

Thank you for your help

1

There are 1 answers

0
Scott Thompson On

Grand Central Dispatch doesn't offer a way to cancel a block after it's been Queued. There is a similar mechanism, NSOperation which does provide a way to cancel a pending operation. Unfortunately, I don't have any source code handy.

But even easier than that might be to add a "cancel" flag and check if you've canceled something before the pop happens.

class TestViewController: UIViewController {
    var secondPushCancelled = false

    @IBAction func pushFirstVC() {
        DispatchQueue.main.async { [weak self] in
            self?.secondPushCancelled = false
            weakSelf?.navigationController?.pushViewController(FirstViewController(), animated: true)
        }
    }

    @IBAction func pushSecondVC() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
            if (self != nil) && !self!.secondPushCancelled {
                self?.navigationController?.pushViewController(SecondViewController(), animated: true)
            }
        }
    }

    func cancelSecondPush() {
        secondPushCancelled = true
    }
}

And finally, if you look at Swift Structured Concurrency, there is a concept of a tree of asynchronous tasks. With the tree, if you cancel a task that owns sub-tasks, then the sub-tasks can determine if they've been cancelled and skip work that is no longer needed.

For more info, watch Explore structured concurrency in Swift