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.
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()
}
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
I have this object:
lockWallTask = DispatchWorkItem(block: {
self.lockWall()
})
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: lockWallTask)
So it is executing after 10 seconds. However, I am trying to pause this item and then resume it. For instance:
I would pause the item after 3 seconds, meaning there is 7 seconds left for execution. I do other stuff for like 5 minutes and then I resume the item and there is still 7 seconds left for execution. I was trying to achieve this life this:
DispatchQueue.resume(task)
DispatchQueue.suspend(task)
However, I was given this compile error:
I don't understand that error. The variable task is of type 'DispatchWorkItem'
How would I achieve pausing, or suspending, a DispatchWorkItem and then resuming it?
You need to use DispatchSource timer where you can pause, resume and stop and perform your operation accordingly.
var timer: DispatchSource?
func resumeTimer() {
guard let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: 0), queue: mainQueue) as? DispatchSource else { return }
timer.setEventHandler {
mainQueue.async(execute: { [weak self] in
self?.runTimer()
})
}
timer.scheduleRepeating(deadline: .now(), interval: .seconds(10), leeway: .seconds(1))
timer.resume()
}
func pausedTimer() {
if let timer = timer {
timer.suspend()
}
}
func stoppedTimer() {
if let timer = timer {
timer.cancel()
}
}
I have a sliderValueChange function which updates a UILabel's text. I want for it to have a time limit until it clears the label's text, but I also want this "timed clear" action to be cancelled & restarted or delayed whenever the UISlider is moved within the time limit before the "timed clear" action takes place.
So far this is what I have:
let task = DispatchWorkItem {
consoleLabel.text = ""
}
func volumeSliderValueChange(sender: UISlider) {
task.cancel()
let senderValue = String(format: "%.2f", sender.value)
consoleLabel.text = "Volume: \(senderValue)"
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3, execute: task)
}
Obviously, this approach does not work, since cancel() apparently cannot be reversed.. (or at least I don't know how). I also don't know how to start a new task at the end of this function which will be cancelled if the function is recalled..
Am I going about this the wrong way? Is there something I am overlooking to make this work?
Use a timer:
weak var clearTimer: Timer?
And:
override func viewDidLoad() {
super.viewDidLoad()
startClearTimer()
}
func startClearTimer() {
clearTimer = Timer.scheduledTimer(
timeInterval: 3.0,
target: self,
selector: #selector(clearLabel(_:)),
userInfo: nil,
repeats: false)
}
func clearLabel(_ timer: Timer) {
label.text = ""
}
func volumeSliderValueChange(sender: UISlider) {
clearTimer?.invalidate() //Kill the timer
//do whatever you need to do with the slider value
startClearTimer() //Start a new timer
}
The problem is that you are cancelling the wrong thing. You don't want to cancel the task; you want to cancel the countdown which you got going when you said asyncAfter.
So use a DispatchTimer or an NSTimer (now called a Timer in Swift). Those are counters-down that can be cancelled. And then you can start counting again.
In earlier versions of Swift, one could create a delay with the following code:
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 4 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
//put your code which should be executed with a delay here
}
But now, in Swift 3, Xcode automatically changes 6 different things but then the following error appears: "Cannot convert DispatchTime.now to expected value dispatch_time_t aka UInt64."
How can one create a delay before running a sequence of code in Swift 3?
After a lot of research, I finally figured this one out.
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { // Change `2.0` to the desired number of seconds.
// Code you want to be delayed
}
This creates the desired "wait" effect in Swift 3 and Swift 4.
Inspired by a part of this answer.
I like one-line notation for GCD, it's more elegant:
DispatchQueue.main.asyncAfter(deadline: .now() + 42.0) {
// do stuff 42 seconds later
}
Also, in iOS 10 we have new Timer methods, e.g. block initializer:
(so delayed action may be canceled)
let timer = Timer.scheduledTimer(withTimeInterval: 42.0, repeats: false) { (timer) in
// do stuff 42 seconds later
}
Btw, keep in mind: by default, timer is added to the default run loop mode. It means timer may be frozen when the user is interacting with the UI of your app (for example, when scrolling a UIScrollView)
You can solve this issue by adding the timer to the specific run loop mode:
RunLoop.current.add(timer, forMode: .common)
At this blog post you can find more details.
Try the following function implemented in Swift 3.0 and above
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
Usage
delayWithSeconds(1) {
//Do something
}
Try the below code for delay
//MARK: First Way
func delayForWork() {
delay(3.0) {
print("delay for 3.0 second")
}
}
delayForWork()
// MARK: Second Way
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// your code here delayed by 0.5 seconds
}
One way is to use DispatchQueue.main.asyncAfter as a lot of people have answered.
Another way is to use perform(_:with:afterDelay:). More details here
perform(#selector(delayedFunc), with: nil, afterDelay: 3)
#IBAction func delayedFunc() {
// implement code
}
Most common things to use are asyncAfter() and Timer. But if blocking thread is OK, then there is an option:
sleep(3) // in seconds
usleep // in 1/million of second
For asynchronous programming (Swift 5.5) pausing in func looks like this:
func someAsyncFunc() async {
await Task.sleep(2_000_000_000) // Two seconds
// Code to be executed with a delay here
}
//Runs function after x seconds
public static func runThisAfterDelay(seconds: Double, after: #escaping () -> Void) {
runThisAfterDelay(seconds: seconds, queue: DispatchQueue.main, after: after)
}
public static func runThisAfterDelay(seconds: Double, queue: DispatchQueue, after: #escaping () -> Void) {
let time = DispatchTime.now() + Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
queue.asyncAfter(deadline: time, execute: after)
}
//Use:-
runThisAfterDelay(seconds: x){
//write your code here
}
I want to pause my app at a certain in point. In other words, I want my app to execute the code, but then at a certain point, pause for 4 seconds, and then continue on with the rest of the code. How can I do this?
I am using Swift.
Using a dispatch_after block is in most cases better than using sleep(time) as the thread on which the sleep is performed is blocked from doing other work. when using dispatch_after the thread which is worked on does not get blocked so it can do other work in the meantime. If you are working on the main thread of your application, using sleep(time) is bad for the user experience of your app as the UI is unresponsive during that time.
Dispatch after schedules the execution of a block of code instead of freezing the thread:
Swift ≥ 3.0
let seconds = 4.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
// Put your code which should be executed with a delay here
}
Swift ≥ 5.5 in an async context:
func foo() async {
try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))
// Put your code which should be executed with a delay here
}
Swift < 3.0
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 4 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
// Put your code which should be executed with a delay here
}
Instead of a sleep, which will lock up your program if called from the UI thread, consider using NSTimer or a dispatch timer.
But, if you really need a delay in the current thread:
do {
sleep(4)
}
This uses the sleep function from UNIX.
Comparison between different approaches in swift 3.0
1. Sleep
This method does not have a call back. Put codes directly after this line to be executed in 4 seconds. It will stop user from iterating with UI elements like the test button until the time is gone. Although the button is kind of frozen when sleep kicks in, other elements like activity indicator is still spinning without freezing. You cannot trigger this action again during the sleep.
sleep(4)
print("done")//Do stuff here
2. Dispatch, Perform and Timer
These three methods work similarly, they are all running on the background thread with call backs, just with different syntax and slightly different features.
Dispatch is commonly used to run something on the background thread. It has the callback as part of the function call
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
print("done")
})
Perform is actually a simplified timer. It sets up a timer with the delay, and then trigger the function by selector.
perform(#selector(callback), with: nil, afterDelay: 4.0)
func callback() {
print("done")
}}
And finally, timer also provides ability to repeat the callback, which is not useful in this case
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(callback), userInfo: nil, repeats: false)
func callback() {
print("done")
}}
For all these three method, when you click on the button to trigger them, UI will not freeze and you are allowed to click on it again. If you click on the button again, another timer is set up and the callback will be triggered twice.
In conclusion
None of the four method works good enough just by themselves. sleep will disable user interaction, so the screen "freezes"(not actually) and results bad user experience. The other three methods will not freeze the screen, but you can trigger them multiple times, and most of the times, you want to wait until you get the call back before allowing user to make the call again.
So a better design will be using one of the three async methods with screen blocking. When user click on the button, cover the entire screen with some translucent view with a spinning activity indicator on top, telling user that the button click is being handled. Then remove the view and indicator in the call back function, telling user that the the action is properly handled, etc.
In Swift 4.2 and Xcode 10.1
You have 4 ways total to delay. Out of these option 1 is preferable to call or execute a function after some time. The sleep() is least case in use.
Option 1.
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.yourFuncHere()
}
//Your function here
func yourFuncHere() {
}
Option 2.
perform(#selector(yourFuncHere2), with: nil, afterDelay: 5.0)
//Your function here
#objc func yourFuncHere2() {
print("this is...")
}
Option 3.
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(yourFuncHere3), userInfo: nil, repeats: false)
//Your function here
#objc func yourFuncHere3() {
}
Option 4.
sleep(5)
If you want to call a function after some time to execute something don't use sleep.
I agree with Palle that using dispatch_after is a good choice here. But you probably don't like the GCD calls as they are quite annoying to write. Instead you can add this handy helper:
public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: #escaping () -> Void) {
let dispatchTime = DispatchTime.now() + seconds
dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}
public enum DispatchLevel {
case main, userInteractive, userInitiated, utility, background
var dispatchQueue: DispatchQueue {
switch self {
case .main: return DispatchQueue.main
case .userInteractive: return DispatchQueue.global(qos: .userInteractive)
case .userInitiated: return DispatchQueue.global(qos: .userInitiated)
case .utility: return DispatchQueue.global(qos: .utility)
case .background: return DispatchQueue.global(qos: .background)
}
}
}
Now you simply delay your code on a background thread like this:
delay(bySeconds: 1.5, dispatchLevel: .background) {
// delayed code that will run on background thread
}
Delaying code on the main thread is even simpler:
delay(bySeconds: 1.5) {
// delayed code, by default run in main thread
}
If you prefer a Framework that also has some more handy features then checkout HandySwift. You can add it to your project via SwiftPM then use it exactly like in the examples above:
import HandySwift
delay(by: .seconds(1.5)) {
// delayed code
}
You can also do this with Swift 3.
Perform the function after delay like so.
override func viewDidLoad() {
super.viewDidLoad()
self.perform(#selector(ClassName.performAction), with: nil, afterDelay: 2.0)
}
#objc func performAction() {
//This function will perform after 2 seconds
print("Delayed")
}
NSTimer
The answer by #nneonneo suggested using NSTimer but didn't show how to do it. This is the basic syntax:
let delay = 0.5 // time in seconds
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(myFunctionName), userInfo: nil, repeats: false)
Here is a very simple project to show how it might be used. When a button is pressed it starts a timer that will call a function after a delay of half a second.
import UIKit
class ViewController: UIViewController {
var timer = NSTimer()
let delay = 0.5
// start timer when button is tapped
#IBAction func startTimerButtonTapped(sender: UIButton) {
// cancel the timer in case the button is tapped multiple times
timer.invalidate()
// start the timer
timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
}
// function to be called after the delay
func delayedAction() {
print("action has started")
}
}
Using dispatch_time (as in Palle's answer) is another valid option. However, it is hard to cancel. With NSTimer, to cancel a delayed event before it happens, all you need to do is call
timer.invalidate()
Using sleep is not recommended, especially on the main thread, since it stops all the work being done on the thread.
See here for my fuller answer.
Try the following implementation in Swift 3.0
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
Usage
delayWithSeconds(1) {
//Do something
}
If you need to set a delay of less than a second, it is not necessary to set the .seconds parameter. I hope this is useful to someone.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
// your code hear
})
You can create extension to use delay function easily (Syntax: Swift 4.2+)
extension UIViewController {
func delay(_ delay:Double, closure:#escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
}
How to use in UIViewController
self.delay(0.1, closure: {
//execute code
})
I believe the simplest and latest way of doing a 4 seconds timer is:
Task {
// Do something
// Wait for 4 seconds
try await Task.sleep(nanoseconds: 4_000_000_000)
}
It uses Swift 5.5's new concurrency.
If your code is already running in a background thread, pause the thread using this method in Foundation: Thread.sleep(forTimeInterval:)
For example:
DispatchQueue.global(qos: .userInitiated).async {
// Code is running in a background thread already so it is safe to sleep
Thread.sleep(forTimeInterval: 4.0)
}
(See other answers for suggestions when your code is running on the main thread.)
DispatchQueue.global(qos: .background).async {
sleep(4)
print("Active after 4 sec, and doesn't block main")
DispatchQueue.main.async{
//do stuff in the main thread here
}
}
To create a simple time delay, you can import Darwin and then use sleep(seconds) to do the delay. That only takes whole seconds, though, so for more precise measurements you can import Darwin and use usleep(millionths of a second) for very precise measurement. To test this, I wrote:
import Darwin
print("This is one.")
sleep(1)
print("This is two.")
usleep(400000)
print("This is three.")
Which prints, then waits for 1 sec and prints, then waits for 0.4 sec then prints. All worked as expected.
Using DispatchQueue's .asyncAfter method you can execute code after given time. So, e.g. execute ... on main thread after 1 second looks like this:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { ... }
Using my handy Delay wrapper struct you can execute it in more fancy way:
struct Delay {
#discardableResult
init(_ timeInterval: TimeInterval, queue: DispatchQueue = .main, executingBlock: #escaping () -> Void) {
queue.asyncAfter(deadline: .now() + timeInterval, execute: executingBlock)
}
}
Usage:
Delay(0.4) { ... }
Swift 5<
Using Task.sleep will not block any code other than the task at hand, and it's pretty straightforward.
//Delay task by 4 seconds:
Task {
try await Task.sleep(nanoseconds: 4000000000)
//Execute your code here
}
This is a simpler way of adding a delay that doesn't affect thread execution.
let continueTime: Date = Calendar.current.date(byAdding: .second, value: 30, to: Date())!
while (Date() < continueTime) {
//DO NOTHING
}
As an alternative solution to the previously proposed options, you can use a delay based on the DispatchGroup class, which is designed to synchronise the execution of several asynchronous tasks:
print("Start")
print(Date())
let delay = DispatchTimeInterval.seconds(3)
let group = DispatchGroup()
group.enter()
_ = group.wait(timeout: .now() + delay)
print("Finish")
print(Date())
Where the enter() method is used to explicitly indicate that the execution of the group code has begun and wait(timeout:) method to wait for group tasks to complete. Of course, in this example this will never happen, for this a timeout is specified, which is equal to the required delay.
It is convenient to use it as a ready-made helper:
public class DispatchWait {
private init () { }
public static func `for` (_ interval: DispatchTimeInterval) {
let group = DispatchGroup()
group.enter()
_ = group.wait(timeout: .now().advanced(by: interval))
}
}
An example of using the DispatchWait:
print("Start")
print(Date())
DispatchWait.for(.seconds(3))
print("Finish")
print(Date())
Unfortunately, I cannot say what is the accuracy of this delay, and what is the probability that the wait(timeout:) method will allow further execution of the program much later than the specified delay.
Also, this solution allows you to delay the code in the current queue, without having to execute it in a separate closure.
this is the simplest
delay(0.3, closure: {
// put her any code you want to fire it with delay
button.removeFromSuperview()
})