use dispatch_time to do task periodically - ios

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

Related

Swift Timer() trouble

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

How do I iterate a loop with time delays in Swift

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)

Multiple Delays in Swift

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)

Format timer label to hours:minutes:seconds in Swift

I have an NSTimer which counts DOWN from 2 hours until 0.
Here are some of my code:
var timer = NSTimer()
let timeInterval:NSTimeInterval = 0.5
let timerEnd:NSTimeInterval = 0.0
var timeCount:NSTimeInterval = 7200.0 // seconds or 2 hours
// TimeString Function
func timeString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
let seconds = time - Double(minutes) * 60
let secondsFraction = seconds - Double(Int(seconds))
return String(format:"%02i:%02i.%01i",minutes,Int(seconds),Int(secondsFraction * 10.0))
}
The Timer Label is:
TimerLabel.text = "Time: \(timeString(timeCount))"
HOWEVER, my timer label shows as:
Time: 200:59.0
How do I format my timer label to look like this:
Time: 01:59:59 // (which is hours:minutes:seconds)?
[Please note that I have no problems with my countdown timer, I only need to know how to CHANGE THE TIME FORMAT using the TimeString function.]
EDIT:
Someone mentioned that my question is a possible duplicate of this one: Swift - iOS - Dates and times in different format. HOWEVER, I am asking on how do I change the time format using the TimeString function that I gave above. I am not asking for another WAY on how to do it.
For instance:
let minutes = Int(time) / 60
gives me "200" minutes. etc.
Your calculations are all wrong.
let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
#rmaddy's solution is accurate and answers the question. However, neither the question nor the solution take into account international users. I suggest using DateComponentsFormatter and let the framework handle the calculations and formatting. Doing so makes your code less error prone and more future proof.
I came across this blog post that provides a concise solution:
http://crunchybagel.com/formatting-a-duration-with-nsdatecomponentsformatter/
Pulled from that post, this is the code snippet that would replace the code you're currently using to make your calculations. Updated for Swift 3:
let duration: TimeInterval = 7200.0
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional // Use the appropriate positioning for the current locale
formatter.allowedUnits = [ .hour, .minute, .second ] // Units to display in the formatted string
formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
let formattedDuration = formatter.string(from: duration)
Swift5
var totalSecond = Int()
var timer:Timer?
call startTimer() based on requirement-
func startTimer(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(countdown), userInfo: nil, repeats: true)
}
#objc func countdown() {
var hours: Int
var minutes: Int
var seconds: Int
if totalSecond == 0 {
timer?.invalidate()
}
totalSecond = totalSecond - 1
hours = totalSecond / 3600
minutes = (totalSecond % 3600) / 60
seconds = (totalSecond % 3600) % 60
timeLabel.text = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
Done
The best way to implement a Timer in Swift (swift 4 works fine).
Declare the variable secs: Int and assign the value, in seconds, of the timer.
Then with the Timer () function, discount one second at a time and pass it to this function.
var secs = 0
var timer = Timer()
func startTimer(segs: Int) {
seg = segs
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerDiscount), userInfo: nil, repeats: true)
}
func timerDiscount() {
let hours = secs / 3600
let mins = secs / 60 % 60
let secs = secs % 60
let restTime = ((hours<10) ? "0" : "") + String(hours) + ":" + ((mins<10) ? "0" : "") + String(mins) + ":" + ((secs<10) ? "0" : "") + String(secs)
}
Declare the variables hours ,minutes and seconds and copy paste the below code it works fine.
if counter > 0 {
let hours = counter / 3600
let minutes = counter / 60
let seconds = counter % 60
counter = counter - 1
timerLbl.text = "\(hours):\(minutes):\(seconds)"
}

NSTimer to to 4 digit label update

I just made a stopwatch with a tutorial but what I would like to do is to update my 00:00 label as 1 second increasing such as 00:01, 00:02: 00:03 and to do the same for minutes. Is there anyway of doing that? Thanks in advance!
Then you have to get the date which will start the counting from which is the current date when a particular event occurs, let's say we will start the timer when the view appears, so implement viewWillAppear as follows:
var currentDate = NSDate()
override func viewWillAppear(animated: Bool) {
currentDate = NSDate()
var timer: NSTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateLabel", userInfo: nil, repeats: true)
timer.fire()
}
and implement the updateLabel function:
func updateLabel() {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
var elapsedSeconds: NSTimeInterval = -self.currentDate.timeIntervalSinceNow
let minutes: Int = Int(elapsedSeconds)/60
let seconds: Int = Int(elapsedSeconds) - (minutes*60)
self.timeLabel.text = String(format: "%02d:%02d", minutes, seconds)
})
}
When formatting time elapsed, NSDateComponentsFormatter is another option:
var start: CFAbsoluteTime!
override func viewDidLoad() {
super.viewDidLoad()
start = CFAbsoluteTimeGetCurrent()
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "handleTimer:", userInfo: nil, repeats: true)
}
lazy var formatter: NSDateComponentsFormatter = {
let _formatter = NSDateComponentsFormatter()
_formatter.allowedUnits = .CalendarUnitMinute | .CalendarUnitSecond
_formatter.zeroFormattingBehavior = .Pad
return _formatter
}()
func handleTimer(timer: NSTimer) {
let elapsed = CFAbsoluteTimeGetCurrent() - start
label.text = formatter.stringFromTimeInterval(elapsed)
}
Admittedly, that will give you the time elapsed in 0:00 format, not 00:00 format.
This is Objective-C, but you'll get the idea:
-(void) updateTotalTime
{
int forHours = timeInSeconds / 3600,
remainder = timeInSeconds % 3600,
forMinutes = remainder / 60,
forSeconds = remainder % 60;
[elapsedTime setString:[NSString stringWithFormat:NSLocalizedString(#"elapsedTime", nil)
,forHours
,forMinutes
,forSeconds]];
}
and in my Localizable.strings:
"elapsedTime" = "Time: %02d:%02d:%02d";

Resources