Home > Enterprise >  Cancel a timer / DispatchQueue.main.asyncAfter in SwiftUI Tap gesture
Cancel a timer / DispatchQueue.main.asyncAfter in SwiftUI Tap gesture

Time:12-09

I am trying to cancel a delayed execution of a function running on the main queue, in a tap gesture, I found a way to create a cancellable DispatchWorkItem, but the issue I have is that it's getting created every time while tapping, and then when I cancel the execution, I actually cancel the new delayed execution and not the first one.

Here is a simpler example with a Timer instead of a DispatchQueue.main.asyncAfter:

.onTapGesture {
    isDeleting.toggle()
    let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
        completeTask()
    }
        
    if !isDeleting {
        timer.invalidate()
    }
 }

completeTask:

  private func completeTask() {
    tasksViewModel.deleteTask(task: task) // task is declared above this func at the top level of the struct and so is tasksViewModel, etc.
    guard let userID = userViewModel.id?.uuidString else { return }
    Task {
        //do some async stuff
    }
}

As you can see if I click it once the timer fires, but if I click it again, another timer fires and straight away invalidates, but the first timer is still running. So I have to find a way to create only one instance of that timer. I tried putting it in the top level of the struct and not inside the var body but the issue now is that I can't use completeTask() because it uses variables that are declared at the same scope. Also, can't use a lazy initialization because it is an immutable struct.

My goal is to eventually let the user cancel a timed task and reactivate it at will on tapping a button/view. Also, the timed task should use variables that are declared at the top level of the struct.

CodePudding user response:

First of all you need to create a strong reference of timer on local context like so:

var timer: Timer?

and then, set the timer value on onTapGesture closure:

.onTapGesture {
    isDeleting.toggle()
    self.timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
        completeTask()
    }
        
    if !isDeleting {
        timer.invalidate()
    }
 }

and after that you can invalidate this Timer whenever you need by accessing the local variable timer like this:

func doSomething() {
    timer?.invalidate()
}

CodePudding user response:

that is my solution mb can help you
var timer: Timer?

private func produceWorkItem(withDelay: Double = 3) {
        scrollItem?.cancel()
        timer?.invalidate()
        scrollItem = DispatchWorkItem.init { [weak self] in
            self?.timer = Timer.scheduledTimer(withTimeInterval: withDelay, repeats: false) { [weak self] _ in
                self?.goToNextPage(animated: true, completion: { [weak self] _ in self?.produceWorkItem() })
                guard let currentVC = self?.viewControllers?.first,
                      let index = self?.pages.firstIndex(of: currentVC) else {
                    return
                }
                self?.pageControl.currentPage = index
            }
        }
        
        scrollItem?.perform()
    }

for stop use scrollItem?.cancel() for start call func

  • Related