How to put delay for each iteration of loop - ios

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()
}

Related

Synchronous start of the event

Is there a way to run the code simultaneously on different devices? Let's say I want that when I click on a button on one of the devices, the function starts simultaneously on both the first and the second device? I tried to use a timer with a time check for 3 seconds ahead, but the function is triggered with a delay of 0.5 seconds on the second device
func getEventTime() -> UInt64{
let now = Date()
let interval = now.timeIntervalSince1970
let result = (UInt64(interval) + (3)) * 1000
return result
}
func getCurrentTime() -> UInt64{
let now = Date()
let interval = now.timeIntervalSince1970
let result = UInt64(interval * 1000)
return result
}
func startTimer(time : UInt64){
Timer.scheduledTimer(withTimeInterval: 0.0001, repeats: true) { timer in
switch getCurrentTime() {
case time - 1000 :
DispatchQueue.main.async {
countdownImageTimer.image = UIImage(named: "Start")
}
break
case time :
DispatchQueue.main.async {
countdownImageTimer.removeFromSuperview()
}
self.setShips()
timer.invalidate()
break
default:
break
}
}
}

asyncAfter not delaying the first execution by specified time

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()
}
}

Delaying the loop (countdown func)

I've looked at many articles, none of the answers are correct. I know that these methods work, but inside a for-loop they don't. How can I create a countdown func - where I can print an array with a one second delay in between.
let array = [9, 8, 7, 6, 5, 4, 3, 2, 1]
I tried this:
for n in array {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print(n)
}
}
And it prints all the numbers after 1 second. So I tried this:
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: (#selector(printer)), userInfo: nil, repeats: true)
}
#objc func printer() {
for n in array {
print(n)
}
}
Same result. I guess these are good methods, but something's wrong.
Create a counter variable and another variable for the timer
var counter = 0
var timer : Timer?
In viewDidLoad create the timer
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(printer), userInfo: nil, repeats: true)
}
In the action method increment the counter variable and invalidate the timer if the number of items in the array is reached.
#objc func printer() {
print(array[counter])
counter += 1
if counter == array.count {
timer?.invalidate()
timer = nil
}
}
Another way is DispatchSourceTimer, it avoids the #objc runtime
var timer : DispatchSourceTimer?
let interval : DispatchTime = .now() + .seconds(1)
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer!.schedule(deadline:interval, repeating: 1)
var index = 0
timer!.setEventHandler {
// DispatchQueue.main.async {
print(array[index])
}
index += 1
if index == array.count {
timer!.cancel()
timer = nil
}
}
timer!.resume()
For loop runs all dispatches nearly at the same time , so all of them with the same waiting Time you can depend it like this
let array = [9, 8, 7, 6, 5, 4, 3, 2, 1]
for i in 0...array.count-1 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) ) {
print(array[i])
}
}
I made countdown this way. Don't know if it is the right way, but it works.
var timeRemaining: Int = 60
countdown()
func countdown() {
if self.timeRemaining > 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1 ) {
self.timeRemaining -= 1
self.countdown()
}
}
}
Try this code
let array = [9, 8, 7, 6, 5, 4, 3, 2, 1]
DispatchQueue.global().async {
for n in array {
sleep(1)
print(n)
}
}
When you use this DispatchQueue is incorrect because it will be held by waiting and then print all

How to Efficiently Schedule Thousands of Async Events Using Swift 3 DispatchQueue

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 :)

unable to loop through a Counter in Swift

I guys I have a working countdown timer which I can set manually and countdown from this value.
What I'm trying to achieve now though is set my INTERVALS counter to say 3 and loop through the countdown timer the amount of times that I set in this counter then when the last interval is complete the timer finishes
sounds straight forward in my head I know I need a loop somewhere but everything I have tried just hast worked this is my current code before adding a loop. Have tried adding a while statement instead of the if statement but that didn't do the job
I've now added this loop but still no joy. Anyone know how to do this?
func runIntervalTimer() {
timerForIntervals = NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector: Selector("updateTimeInterval"), userInfo: nil, repeats: true)
}
func updateTimeInterval() {
do {
if intervalCountdown > 0 {
intervalHasBeenSet = true
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60) }
} while intervalHasBeenSet == true
intervalHasBeenSet = false
intervalCountdown = 0
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func InterValTimerIncrease(sender: AnyObject) {
intervalCountdown++
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func IntervalTimerDecrease(sender: AnyObject) {
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
#IBAction func IntervalStart(sender: AnyObject) {
runIntervalTimer()
}
You're probably in an infinite loop here:
do {
if intervalCountdown > 0 {
intervalHasBeenSet = true
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60) }
} while intervalHasBeenSet == true
Your condition is intervalHasBeenSet == true but it's never being set to false inside the loop, so it can never exit.
If you want the timer to count down each interval, I think all you need to do is:
func updateTimeInterval() {
println(intervalCountdown)
if intervalCountdown > 0 {
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
}
Because the updateTimeInterval should be called each interval by NSTimer, you shouldn't need a loop inside this function.
To have the timer count down multiple times, you need an extra variable (intervalNum):
func updateTimeInterval() {
println(intervalCountdown)
if intervalCountdown > 0 && intervalNum > 0 {
// countdown in the current interval
intervalCountdown--
IntervalTimer.text = String(format: "%02d:%02d", intervalCountdown/60,
intervalCountdown%60)
}
else
{
// move on to the next interval
intervalNum--
intervalCountdown = 20 // or grab the original value from somewhere
}
if (intervalNum == 0)
{
// stop the timer
timerForIntervals.invalidate()
}
}

Resources