Delaying the loop (countdown func) - ios

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

Related

how to remove cell index when timer gets complete after 5 min ios swift 5 , when called api not repeat timeragain of same index

I want to implement timer logic, when 5 min gets complete then my Tableview reload and its remove that particular index, I have tried not gets works, and timer get fast
//Timer ACtion Method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
Set Timer value nil, And check when API called then the timer will not pass any selector method
#objc func timerAction() {
if seconds>0 {
seconds-=1
minutes = String(format:"%02i",(seconds / 60))
seconds1 = String(format:"%02i",(seconds % 60))
print(minutes! + ":" + seconds1!)
self.lblMin.text = minutes!
self.lblSec.text = seconds1!
} else {
minutes = String(seconds / 60)
seconds1 = String(seconds % 60)
if minutes == "0" && seconds1 == "0" {
timer.invalidate()
timer = nil
btnReject.isUserInteractionEnabled = false
btnAccept.isUserInteractionEnabled = false
// TBVC.InstancePending.arrPending.remove(at: intValue!)
//tblData?.deleteRows(at: [IndexPath(row: intValue!, section: 1)], with: .automatic)
// TBVC.InstancePending.getTableBooking(strStatus: "0")
// TBVC.InstancePending.strTap = "Pending"
// TBVC.InstancePending.segment.selectedSegmentIndex = 0
// tblData?.reloadData()
}
}
}
=====================================
2nd method to implement timer:-
Initialize Variable
var timer:Timer?
var totalMinut:Int = 2
var totalSecond:Int = 120
var timeLeft = 120
Add timer function
func setupTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
}
#objc func onTimerFires() {
var minutes: Int
var seconds: Int
if totalSecond == 1 {
timer?.invalidate()
timer = nil
}
totalSecond = totalSecond - 1
minutes = (totalSecond) / 60
seconds = (totalSecond) % 60
timerLabel.text = String(format: "%02d:%02d", minutes, seconds)
}
Call "setUpTimer" method where you have required. In my case, I have called it in the "viewDidLoad" method of a view controller
override func viewDidLoad() {
super.viewDidLoad()
setupTimer()
}

How to put delay for each iteration of loop

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

Swift: How to stop timers started from a loop?

I got some Swift code to print every character of a specific word ("stackoverflow") and also with a specific delay (1.0s).
To understand my thoughts look at the pseudo code:
print("s")
wait(1s)
print("t")
wait(1s)
print("a")
wait(1s)
print("c")
wait(1s)
print("k")
wait(1s)
...
Ok - below you can find my code written in Swift:
var mystring="stackoverflow"
var counter=0.0
for i in mystring {
Timer.scheduledTimer(
timeInterval: Double(counter),
target: self,
selector: #selector(self.myfunc(_:)),
userInfo: String(i),
repeats: false)
counter=counter+1.0
})
}
func myfunc(_ timer: Timer) {
let value: String? = timer.userInfo! as? String
print ("Value: \(value as String?)")
}
But how is it possible to kill all myfunc-calls after the for loop has finished? How to kill the different Timers that I didn't declared with a variable to avoid the override of the last Timer??
Why not one timer and a few lines additional logic. The code prints the first character of the string when the timer fires and then drops the first character until the string is empty. At the end the timer gets invalidated.
let string = "stackoverflow"
var temp = Substring(string)
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(temp.prefix(1))
temp = temp.dropFirst()
if temp.isEmpty { timer.invalidate() }
}
or as ticker
let string = "stackoverflow"
var counter = 1
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print(string.prefix(counter))
if counter == string.count { timer.invalidate() }
counter += 1
}
Edit : For those who argue that this is not the best answer it's the best for his current programming technique and to suggest a good one here is a solution in edit part for his main issue with no timers at all
Edit://////
var counter = 0.0
for i in mystring {
DispatchQueue.main.asyncAfter(deadline: .now() + counter) {
print("\(String(i))")
}
counter = counter + 1
}
/////////
declare var
var timerarr = [Timer] = []
then add every created timer to the array
for i in mystring {
let t = Timer.scheduledTimer(
timeInterval: Double(counter),
target: self,
selector: #selector(self.myfunc(_:)),
userInfo: String(i),
repeats: false)
counter=counter+1.0
})
timerarr.append(t)
}
and loop to stop
for i in 0..<timerarr.count {
let tim = timerarr[i]
tim.invalidate()
}
timearr = []

The best way of adding countdown label with timer in UICollectionViewCell which works fine even after cell is being reused

I have a list of items which are presenting to the user in UICollectionView. These items have a countdown label to show the remaining time that the item is available.
I used a Timer in UICollectionViewCell to show the remaining time like:
OperationQueue.main.addOperation {
var remaingTimeInterval = self.calculateRemainigTime(remainingTime: remainingTime)
if remaingTimeInterval > 0 {
self.timerLabel.isHidden = false
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (_) in
let hours = Int(remaingTimeInterval) / 3600
let minutes = Int(remaingTimeInterval) / 60 % 60
let seconds = Int(remaingTimeInterval) % 60
self?.timerLabel?.text = String(format:"%02i:%02i:%02i", hours, minutes, seconds)
remaingTimeInterval -= 1
})
} else {
self.timer?.invalidate()
self.timerLabel.isHidden = true
}
}
and that's how I calculate the remaining time based on the given Date:
//Calculating remaining time based on the item endDate and currentDAte
func calculateRemainigTime(remainingTime: String) -> Int {
let prizeRemainingTime = Helper.stringToDate(remainingTime)
let prizeRemainingTimeInterval = Int(prizeRemainingTime.timeIntervalSince1970)
let currentTimeInterval = Int(Date().timeIntervalSince1970)
return prizeRemainingTimeInterval - currentTimeInterval
}
Everything works fine till the cell is being reused, after that the countdown numbers are not correct anymore.
Is this a correct way to show the countdown in UICollectionViewCell or there is a better solution.
Can anyone help me to find a way through this?
Move the timer logic to the data model.
Instead of target/action use the block-based API of Timer.
In cellForRow pass the timer callback block to the cell.
When the timer fires the code in the callback block can update the UI in the cell.
Coding from accepted answer guideline, hope you like it.
MyViewController
protocol MyViewControllerDelegate : class {
func updateTimer(_ timeString: String)
}
class MyViewController: UIViewController {
weak var timerDetegate: MyViewControllerDelegate?
var timer = Timer()
var time = 0
fun ViewDidLoad() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
self.time += 1
self.timerDetegate?.updateTimer("time \(self.time)")
})
}
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewdequeueReusableCell(withIdentifier: "MeCell")
vc.timerDetegate = cell
return cell
}
...
}
MyTableViewCell
class MyTableViewCell : UITableViewCell, MyViewControllerDelegate {
...
func updateTimer(_ timeString: String) {
lblTimeLeft.text = timeString
}
}
I had implemented this simpler way (without caring about the performance/latency)
in ..cellForItemAt.. method, I call
cell.localEndTime = yourEndTimeInterval
cell.startTimerProgress()
In collection view cell, I added a method which starts the progress:
var localEndTime: TimeInterval = 0 //set value from model
var timer:Timer?
func timerIsRunning(timer: Timer){
let diff = localEndTime - Date().timeIntervalSince1970
if diff > 0 {
self.showProgress()
return
}
self.stopProgress()
}
func showProgress(){
let endDate = Date(timeIntervalSince1970: localEndTime)
let nowDate = Date()
let components = Calendar.current.dateComponents(Set([.day, .hour, .minute, .second]), from: nowDate, to: endDate)
self?.timerLabel?.text = String(format:"%02i:%02i:%02i", components.hour!, components.minute!, components.second!)
}
func stopProgress(){
self?.timerLabel?.text = String(format:"%02i:%02i:%02i", 0, 0, 0)
self.timer?.invalidate()
self.timer = nil
}
func startTimerProgress() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerIsRunning(timer:)), userInfo: nil, repeats: true)
self.timer?.fire()
}

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