I'm trying to make a delay of less than a second. I found this code from the web. It doesn't however accept delays of less than a second. The Grand Dispatch Concept in Swift is a bit of a mystery to me. How should I modify this code to create a delay of 0.3 seconds?
let deadlineTime = DispatchTime.now() + .seconds(1) //how to get 0.3 seconds here
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
//code here
}
Well, that's quite easy, don't use seconds, use milliseconds:
let deadlineTime = DispatchTime.now() + .milliseconds(300) // 0.3 seconds
just add your reqired time to DispatchTime.now() and you will get a result
let deadlineTime = DispatchTime.now() + 0.3 //Here is 0.3 second as per your requirement
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
//code here
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
// your method after delay
}
Remember, this will execute on main thread. If you want otherwise, check DispatchQueue.global(qos:) or this reference
use this, will work
let delay = 2.5 * Double(NSEC_PER_SEC)
let time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: time, execute: {
self.view_Alert.alpha = 0
})
Related
I'm working on an app where I'm using a timer to count down time left in a workout and also count up for total time. My counters are out of sync, looks like it's less than a second off. I'm wondering if it has something to do with #Publish, maybe one fires before the other. Any idea what's happening and how to fix it?
class TimeManager: ObservableObject {
#Published var totalTime: Double = 0.0
#Published var timeRemaining: Double = 180.0
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in
guard let self = self else { return }
self.timeRemaining -= 0.1
self.totalTime += 0.1
}
}
}
then my view
#ObservedObject var timeManager = TimeManager()
...
var body: some View {
VStack {
let time = timeManager.timeRemaining
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
ZStack {
Progress()
Text(String(format:"%02i:%02i", minutes, seconds))
.font(.system(size: 60))
}
let total = timeManager.totalTime
let totalMins = Int(total) / 60 % 60
let totalSecs = Int(total) % 60
Text(String(format:"%02i:%02i", totalMins, totalSecs))
.font(.system(size: 40))
}
}
Your time values are in sync. The reason for the behaviour you are seeing is the Double / Int conversions and the rounding applied while display the Texts. Try this line:
Text("\(timeManager.timeRemaining + timeManager.totalTime)")
and you will see this allways adding up to 180.
You could try Int values in your Viewmodel decrementing/incrementing by 1 and a DateComponentsFormatter to format the values in your View.
let componentsFormatter = DateComponentsFormatter()
Text("\(componentsFormatter.string(from: Double(timeManager.timeRemaining)) ?? "NAN")")
.font(.system(size: 60))
You would of course need to tweek the formatter to display the time the way you want it to be. But I agree with Paulw11. This seems like a bad design. It would be better to have a single source of truth as a Date and go from there.
Maybe calculate your 2nd value based on the first when you decrement the time. For example like this:
remaining = (180 - total) >= 0 ? (180 - total) : 0
I'm trying to animate a textview to get the string characters to appear one by one, then also disappear one by one starting with the first character after a 0.5 second delay.
I am close, the only issue I have is that the very first character gets removed immediately so it's as if it never appeared. Any ideas, here's my function:
extension UITextView {
func animate(newText: String) {
DispatchQueue.main.async {
self.text = ""
for (index, character) in newText.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 * Double(index)) {
self.text?.append(character)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5 * Double(index)) {
self.text?.remove(at: newText.startIndex)
}
}
}
}
}
The problem is that the first character has an index of 0, so the delay is .now() + 0.5 * 0, which simplifies to just .now().
Add a constant to the delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5 * Double(index) + 0.5) {
^^^^^^
This will cause the first character to disappear 1 second later.
Alternatively:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5 * Double(index + 1)) {
In addition, using a Timer here can be more suitable if your text is long, as Rob has said n the comments.
var index = 0
let characterArray = Array(newText)
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (timer) in
textView.text! += "\(characterArray[index])"
index += 1
if index == characterArray.endIndex {
timer.invalidate()
}
}
Say I had this loop:
count = 0
for i in 0...9 {
count += 1
}
and I want to delay it.
Delay function:
// Delay function
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)
}
.
This means if I want to increase count by 1 every second, I would do:
count = 0
for i in 0...9 {
delay(1) {
count += 1
}
}
but this doesn't work as it only delays code in brackets. How do I delay the actual loop? I would like the delay to stop from iterating until the time has passed, and then the loop/code can repeat again.
Your current code doesn't work because you are doing the increment asynchronously. This means that the for loop will still run at its normal speed.
To achieve what you want, you can use a timer like this:
var count = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
count += 1
print(count)
}
If you want it to stop after 5 times, do this:
var count = 0
var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ t in
count += 1
print(count)
if count >= 5 {
t.invalidate()
}
}
As #Paulw11 and #Sweeper have suggested, you can use a Timer to do this. However, if the code does need to be asynchronous for some reason, you can reimplement the loop asynchronously by making it recursive:
func loop(times: Int) {
var i = 0
func nextIteration() {
if i < times {
print("i is \(i)")
i += 1
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
nextIteration()
}
}
}
nextIteration()
}
I am moving a map marker 1 metre every 0.1 seconds with the following code:
for index in 1 ... points.count - 1 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 * Double(index)) {
self.driverMarker.position = points[index]
self.driverMarker.map = self.mapView
}
}
If the distance of all the points is 3000 metres then I set setting 3000 asyncAfters and I am worried this is inefficient.
Is there a better way to do this?
From your requirements stated in the question and comments, I believe using DispatchSourceTimer is better for this task. I provided a sample code for reference below.
var count = 0
var bgTimer: DispatchSourceTimer?
func translateMarker() {
if count == (points.count - 1) {
bgTimer?.cancel()
bgTimer = nil
return
}
self.driverMarker.position = points[index]
self.driverMarker.map = self.mapView
count += 1
}
override func viewDidLoad() {
super.viewDidLoad()
let queue:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
bgTimer = DispatchSource.makeTimerSource(flags: [], queue: queue)
bgTimer?.scheduleRepeating(deadline: DispatchTime.now(), interval: 0.1)
bgTimer?.setEventHandler(handler: {
self.translateMarker()
})
bgTimer?.resume()
}
Please let me know if you are facing any issues in implementing this. Feel free to suggest edits to make this better :)
So basically I'm trying to print the word "yo" 20 times with a 2 second time delay between each iteration. This is what I came up with which doesn't work
var j = 0
while(j < 20){
print("yo")
let seconds = 2.0
let delay = seconds * Double(NSEC_PER_SEC)//nanoseconds per seconds
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
j+=1
}
}
Who knows the right way to go about this? Thanks in advance.
Try this. It creates 20 print yo closures at one time instead of serially delaying between each one.
let delay = 2.0 * Double(NSEC_PER_SEC)
(1...20).map {
iteration in
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(iteration)))
dispatch_after(time, dispatch_get_main_queue()) {
print("yo")
}
}
Your code is close. You need to put the print statement inside the dispatch_after:
var j: UInt64 = 0
let seconds: UInt64 = 1
while(j < 10)
{
let delay = seconds * j * NSEC_PER_SEC //nanoseconds per seconds
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue())
{
print("yo")
}
j += 1
}
print("Should start \"yo'ing\" soon")
Also your math was off. the delay value to dispatch_time is a UInt64, not a double.
Note that the code above probably won't work in a playground, since as soon as the main code path finishes, it terminates.
You may try this function
func repeatedPrint(count: Int, withDelay delay: Double)
{
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue())
{
if count < 1 {return}
print("yo")
self.repeatedPrint(count - 1, delay: delay)
}
}
repeatedPrint(20, delay: 2)