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)
Related
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've done a simple timer in Swift. All is well apart from when the seconds reach 59 seconds. Instead of going back to zero they just carry on going. Would someone would be able to point out where I'm going wrong and why this is happening?
#IBAction func startButtonDidTouch(_ sender: Any) {
if !timerIsRunning{
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
timerIsRunning = true
}
}
#objc func updateTimer() {
totalSeconds += 0.01
let totalSecondsTimes100: Int = Int(totalSeconds*100)
let minutes = Int(totalSeconds/60)
let timerChoice = Double(minutes)
let minStr = (minutes == 0) ? "00" : "0\(minutes)"
let secStr = (totalSeconds < 9) ? "0\(Float(totalSecondsTimes100)/100)" : "\(Float(totalSecondsTimes100)/100)"
switch Int(timerChoice) {
case Int(timerCountdownLabel.text!)!:
timerLabel.text = "\(minStr):\(secStr)"
audioPlayer.play()
timer.invalidate()
timerIsRunning = false
default:
timerLabel.text = "\(minStr):\(secStr)"
}
}
You should calculate the seconds as:
let seconds = totalSeconds % 60
and then use seconds in your calculation of secStr instead of using totalSeconds.
There are better ways to write your code:
#objc func updateTimer() {
totalSeconds += 0.01
let minutes = Int(totalSeconds) / 60
let seconds = totalSeconds.remainder(dividingBy: 60)
let timeStr = String(format: "%02d:%06.3f", minutes, seconds)
timerLabel.text = timeStr
if Int(timerCountdonwLabel.text!)! == minutes {
audioPlayer.play()
timer.invalidate()
timerIsRunning = false
}
}
And you really shouldn't keep track of time simply by adding 0.01 to totalSeconds. A Timer is not accurate. Your clock will drift over time. It's best to save a timestamp (Date()) when you start the timer and get the current timestamp (Date()) inside updateTimer and get the difference between the two.
Here is a timer function that outputs format minutes:seconds:milliseconds, compare with your code and you'll find what's wrong with your code.
private weak var timer: Timer?
private var startTime: Double = 0
private var elapsed: Double = 0
private var time: Double = 0
private func startTimer(){
startTime = Date().timeIntervalSinceReferenceDate - elapsed
timer = Timer.scheduledTimer(timeInterval: (0.01), target: self, selector: #selector(updateTimeLabel), userInfo: nil, repeats: true)
}
private func stopTimer(){
elapsed = Date().timeIntervalSinceReferenceDate - startTime
timer?.invalidate()
}
#objc func updateTimeLabel(){
time = Date().timeIntervalSinceReferenceDate - startTime
let minutes = UInt8(time / 60.0)
let timeNoMin = time - (TimeInterval(minutes) * 60)
let seconds = UInt8(timeNoMin)
let timeNoSec = timeNoMin - (TimeInterval(seconds))
let milliseconds = UInt16(timeNoSec * 100)
let strMinutes = String(minutes)
var strSeconds = ""
if strMinutes == "0" {
strSeconds = String(seconds)
}
else {
strSeconds = String(format: "%02d", seconds)
}
let strMilliseconds = String(format: "%02d"), milliseconds)
if strMinutes != "0" {
timerLabel.text = "\(strMinutes):\(strSeconds).\(strMilliseconds)"
}
else {
timerLabel.text = "\(strSeconds).\(strMilliseconds)"
}
}
To get minutes and seconds from a floating point total number of seconds elapsed, elapsed you can:
To get minutes, divide by 60.0 and truncate to the nearest integer:
let minutes = Int(elapsed / 60)
To get seconds, get the remainder, either via:
let seconds = elapsed - Double(minutes) * 60
Or
let seconds = elapsed.truncatingRemainder(dividingBy: 60)
A couple of other observations:
There's no point in running a timer every 0.01 seconds when the screen refresh rate is usually capped at 60 frames per second. If you want to update it with the greatest frequency, use a CADisplayLink which is timed not only for maximum screen refresh rate, but also fires optimally to allow the update to happen before the next frame is to be rendered.
You should not use timer to increment the elapsed time by 0.01 (or any fixed interval) because you have no assurances that it will actually fire with that frequency. If something, for example, momentarily blocks the main thread by 200 milliseconds, you don't want this to affect your calculation of the amount of time that has elapsed.
Instead, save the start time when the timer starts, and every time the timer fires recalculate the elapsed time and format the results accordingly.
To complicate this further, you should not even be comparing Date() instances (or CFAbsoluteTimeGetCurrent() values) because, as the documentation warns us:
Repeated calls to this function do not guarantee monotonically increasing results. The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock.
Instead, you should use a mach_absolute_time based calculation (such as returned by CACurrentMediaTime()), for which repeated calls are assured to return accurately elapsed time calculations.
The only time you should use Date() or CFAbsoluteTimeGetCurrent() if your app is saving the start time in persistent storage, to be retrieved later when the app is restarted (possibly after a device reboot) to render the effect of the elapsed time between starts of an app. But this is a pretty narrow edge case.
Anyway, this yields:
var start: CFTimeInterval?
weak var displayLink: CADisplayLink?
func startTimer() {
self.displayLink?.invalidate() // just in case timer had already been started
start = CACurrentMediaTime()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.preferredFramesPerSecond = 100 // in case you're using a device that can render more than 60 fps
displayLink.add(to: .main, forMode: .commonModes)
self.displayLink = displayLink
}
#objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CACurrentMediaTime() - start!
let minutes = Int(elapsed / 60)
let seconds = elapsed - Double(minutes) * 60
let string = String(format: "%02d:%05.2f", minutes, seconds)
label.text = string
}
func stopTimer() {
displayLink?.invalidate()
}
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
})
I'm working on a simple card game, in which after a player presses a button, three AI computers will take their turns one after another. However, I need there to be a pause between each turn.
This is what I need:
playerButton > PAUSE > computer1Goes > PAUSE > computer2Goes > PAUSE > computer3Goes
Code:
#IBAction func placeCardAction(sender: UIButton) {
// playerButton does this action
var playerCardOnTop = game!.player.deck.placeCard()
middleDeck.addSingleCard(playerCardOnTop)
updateCardCount()
// Start computer actions
let delay = 2.0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
self.game?.computer1PlacesCard(&self.middleDeck)
self.updateCardCount()
}
let delay2 = 2.0 * Double(NSEC_PER_SEC)
let time2 = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time2, dispatch_get_main_queue()) {
self.game?.computer2PlacesCard(&self.middleDeck)
self.updateCardCount()
}
let delay3 = 2.0 * Double(NSEC_PER_SEC)
let time3 = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time3, dispatch_get_main_queue()) {
self.game?.computer3PlacesCard(&self.middleDeck)
self.updateCardCount()
}
}
Unfortunately, all the delays start/end at the same time, so what ends up happening is that all of the computer functions run at the same time instead of taking turns, one after another.
If anyone can help solve this problem, I would appreciate it!
Easiest solution.... change delay2 to 4 and delay3 to 6. As it stands right now, of course they all go off at the same time, the all have the same delay.
Alternatively, stack them like:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay))) {
// step one
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay))) {
// step two
...
}
}
I suggest using array of Computer objects which can perform placeCard function
#IBAction func placeCardAction(sender: UIButton) {
// playerButton does this action
var playerCardOnTop = game!.player.deck.placeCard()
middleDeck.addSingleCard(playerCardOnTop)
updateCardCount()
self.computersPlaceCards(0)
}
private func computersPlaceCards(i: Int) {
if self.game == nil || i >= self.game!.computers.count {
return
}
let delay = 2.0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
self.game?.computers[i].placeCards(&self.middleDeck)
self.updateCardCount()
self.computersPlaceCards(i+1)
}
}
You can use sleep() inside a dispatch_async:
dispatch_async( dispatch_get_global_queue( QOS_CLASS_USER_INTERACTIVE, 0 ) ) {
dispatch_async(dispatch_get_main_queue()) {
print("first")
}
sleep(1)
dispatch_async(dispatch_get_main_queue()) {
print("second")
}
sleep(1)
dispatch_async(dispatch_get_main_queue()) {
print("third")
}
sleep(1)
dispatch_async(dispatch_get_main_queue()) {
print("fourth")
}
}
(My answer previously used NSOperationQueue--either will work)
I try to use dispatch_time to periodically call doTask() recursively with a interval of 600 seconds. Here is my simple code:
private func doTask() -> Void {
var interval : NSTimeInterval = someService.getInterval() //it is 600
NSLog("interval = \(interval)"); //Like I said, it is 600, because I see the log
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
//recursively call doTask() after 600 seconds
dispatch_after(dispatchTime,
GlobalBackgroundQueue,
{self.doTask()}
)
}
Here is the GlobalBackgroundQueue:
var GlobalBackgroundQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)
}
But when I call the doTask() function, at runtime, the interval between each call on doTask() is 15 seconds. Why? Why the interval is not 600 seconds?
=====UPDATE=====
I also tried NSTimer:
NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "doTask", userInfo: nil, repeats: true)
But at runtime, the doTask() get called every 20 seconds. Still not 600 seconds.
try this
let delay: UInt64 = 600
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * NSEC_PER_SEC))
This works:
let delay: NSTimeInterval = 600
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) * delay))
dispatch_after(time, dispatch_get_main_queue(), { () -> Void in
})