WKInterfaceTimer used as a timer to countdown start and stop - ios

I am trying to create a timer to countdown x minutes and y seconds.
I am computing the number of seconds and creating the InterfaceTimer like this:
timer.setDate(NSDate(timeIntervalSinceNow:Double(secondsValue+1)))
timer.stop()
after that I keep stoping it and starting it again and again, but the values are suddenly decreasing as "time(now) doesn't stop".
Eg: if the timer shows :55, I start it for 3sec and stop it, it shows :52, I wait 10seconds and then start it again, it starts from :42.
I can not save the value currently in the WKInterfaceTimer, so that I could start again from the same point. Everything I tried doesn't work. Did anyone work with the timer and it stayed at the same value after stopping it?

Yes the watchkit timer is a bit...awkward...and definitely not very intuitive. But that's just my opinion
You'll have to keep setting the date/timer each time the user chooses to resume the timer.
Remember, you'll also need an internal NSTimer to keep track of things since the current WatchKit timer is simply for display without having any real logic attached to it.
So maybe something like this...It's not elegant. But it works
#IBOutlet weak var WKTimer: WKInterfaceTimer! //watchkit timer that the user will see
var myTimer : NSTimer? //internal timer to keep track
var isPaused = false //flag to determine if it is paused or not
var elapsedTime : NSTimeInterval = 0.0 //time that has passed between pause/resume
var startTime = NSDate()
var duration : NSTimeInterval = 45.0 //arbitrary number. 45 seconds
override func willActivate(){
super.willActivate()
myTimer = NSTimer.scheduledTimerWithTimeInterval(duration, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)
WKTimer.setDate(NSDate(timeIntervalSinceNow: duration ))
WKTimer.start()
}
#IBAction func pauseResumePressed() {
//timer is paused. so unpause it and resume countdown
if isPaused{
isPaused = false
myTimer = NSTimer.scheduledTimerWithTimeInterval(duration - elapsedTime, target: self, selector: Selector("timerDone"), userInfo: nil, repeats: false)
WKTimer.setDate(NSDate(timeIntervalSinceNow: duration - elapsedTime))
WKTimer.start()
startTime = NSDate()
pauseResumeButton.setTitle("Pause")
}
//pause the timer
else{
isPaused = true
//get how much time has passed before they paused it
let paused = NSDate()
elapsedTime += paused.timeIntervalSinceDate(startTime)
//stop watchkit timer on the screen
WKTimer.stop()
//stop the ticking of the internal timer
myTimer!.invalidate()
//do whatever UI changes you need to
pauseResumeButton.setTitle("Resume")
}
}
func timerDone(){
//timer done counting down
}

Related

Timer is not running more than 3 minutes in background

I am working on meditation app. In this app i have some musical content and some silent meditation section using timer. Timer is working fine when it is in foreground but it is running for only 3 min in background(when device is locked or user press home button to exit from the app). I am using swift4. What i have tried :
var timer: Timer!
var timeCounter : Int!
var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
var backgroundTask = BackgroundTask()
#IBOutlet weak var timeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier!)
})
backgroundTask.startBackgroundTask()
DispatchQueue.global(qos: .background).async {
let currentRunLoop = RunLoop.current
let timeInterval = 1.0
self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
self.timer.tolerance = timeInterval * 0.1
currentRunLoop.add(self.timer, forMode: .commonModes)
currentRunLoop.run()
}
}
}
#objc func updateTimer () {
timeCounter = timeCounter - 1
let minutes = Int(timeCounter) / 60 % 60
let seconds = Int(timeCounter) % 60
print("timeCounter", timeCounter)
if (timeCounter == 0){
if self.timer != nil {
self.timer?.invalidate()
self.timer = nil
player.play()
}
}
timeLabel.fadeTransition(0.4)
timeLabel.text = String(format: "%02i:%02i",minutes,seconds)
}
Thanks in Advance.
In general you get limited time from the OS to run in background. You can check and react to the background time left using:
UIApplication.shared.backgroundTimeRemaining
If conditions are good (device is unlocked, battery full ...) you typically get about 160-180 seconds.
You find detailed information in Apples documentation.
As you want to play audio, you can use "Plays Audio" background mode to not get cut by the OS:
Depending how you play audio, configuring the AudioSession might also improve things.
Edit:
How I understand now from your comment, you want your app to do something every 4 minutes. The only possiblility I see is to use the BackgroundFetch feature. This does not guarantee a fixed interval though.

Stopwatch not synced

Sorry if this is a newbie question, I am very new to iOS & Swift. I have a problem with the timer interval: I set 0.01 time interval but it doesn't correspond with the timer label, because 0.01 corresponds in one millisecond but it doesn't show it. So basically the timer is skewed.
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateStopwatch) , userInfo: nil, repeats: true)
#IBAction func startStopButton(_ sender: Any) {
buttonTapped()
}
func updateStopwatch() {
milliseconds += 1
if milliseconds == 100 {
seconds += 1
milliseconds = 0
}
if seconds == 60 {
minutes += 1
seconds = 0
}
let millisecondsString = milliseconds > 9 ?"\(milliseconds)" : "0\(milliseconds)"
let secondsString = seconds > 9 ?"\(seconds)" : "0\(seconds)"
let minutesString = minutes > 9 ?"\(minutes)" : "0\(minutes)"
stopWatchString = "\(minutesString):\(secondsString).\(millisecondsString)"
labelTimer.text = stopWatchString
}
func buttonTapped() {
if isTimerRunning {
isTimerRunning = !isTimerRunning
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateStopwatch) , userInfo: nil, repeats: true)
startStopButton.setTitle("Stop", for: .normal)
}else{
isTimerRunning = !isTimerRunning
timer.invalidate()
startStopButton.setTitle("Start", for: .normal)
}
}
Devices have maximum screen update rate (most are 60 fps), so there is no point in going faster than that. For maximum screen refresh rate, use a CADisplayLink rather than a Timer, which is coordinated perfectly for screen refreshes (not only in frequency, but also the timing within the screen refresh cycle).
Also don't try to keep track of the time elapsed by adding some value (because you are not guaranteed that it will be called with the desired frequency). Instead, before you start your timer/displaylink, save the start time and then when the timer/displaylink is called, display the elapsed time in the desired format.
For example:
var startTime: CFTimeInterval!
weak var displayLink: CADisplayLink?
func startDisplayLink() {
self.displayLink?.invalidate() // stop prior display link, if any
startTime = CACurrentMediaTime()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .current, forMode: .commonModes)
self.displayLink = displayLink
}
func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CACurrentMediaTime() - startTime
let minutes = Int(elapsed / 60)
let seconds = elapsed - CFTimeInterval(minutes) * 60
let string = String(format: "%02d:%05.2f", minutes, seconds)
labelTimer.text = string
}
func stopDisplayLink() {
displayLink?.invalidate()
}
Note, CACurrentMediaTime() uses mach_time, like hotpaw2 correctly suggested, but does the conversion to seconds for you.
The time delay of a scheduledTimer is only approximate, and can differ from what is requested by many milliseconds, due to iOS overhead. A repeating Timer is even worse for timing, as any delay jitter errors will accumulate. So don't use a Timer for timing longer events.
A CADisplayLink is a more reliable timer, as it is synchronized to the 60 Hz display refresh (e.g. this is the maximum rate that any UILabel can be changed on devices other than the latest iPad Pros). There is no use trying to update a time display any faster (except possibly on the latest iPad Pros).
Also, do not use Date methods for timing, as they are not guaranteed to be monotonic when the device is connected to a network (as NTP can change the clock time right in the middle of your timing activity).
You should check any elapsed time measurement UI against one of the built-in timers such as mach_time. mach_absolute_time() is guaranteed to be monotonic, and not affected by NTP or other network activity.

NSTimer/Timer duration and UI update

Probably a simple question, however, I can't seem to conceive an elegant approach.
I want to design a trivial application that allows a timer to be set to a duration (say 1 minute), after that duration the timer should expire, in the duration the timer should update the UI every second.
Thus if the timer is set to one minute, the timer should fire, update the UI every second (by calling a method) and then finally invalidate after 1 minute.
My conundrum is, I can set a scheduledTimerWithInterval, it calls a method on the timer interval. If I make this 1 minute I can call a method after one minute to invalidate the timer, but there doesn't seem to be a mechanism to perform calls during this minute.
Can you give me some guidance?
I would have done something like this:
1: Declare a timer and a counter:
var timer = Timer()
var counter = 0
2: Create a new function called for example startEverySecond that starts the timer with one second interval, this function is called after 60 seconds and will be called for 1 minute and then invalidate:
func startEverySecond() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(everySecond), userInfo: nil, repeats: true)
}
func everySecond(){
if counter == 60{
timer.invalidate()
}
counter = counter + 1
print("Second")
}
3: Handle the start and stop of the timer:
// Start with 60 second interval
timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(startEverySecond), userInfo: nil, repeats: false)
// Stop
timer.invalidate()
So basically when you start your timer it will start off with 60 seconds and when this is done it will call the function startEverySecondand this will change the timer to call the everySecondfunction every second for 1 minute. To stop the timer just call timer.invalidate()
Swift 3.x code
Make two global variables
var seconds = 1
var secondTimer:Timer?
Then make these two functions
// for setup of timer and variable
func setTimer() {
seconds = 1
secondTimer?.invalidate()
secondTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateUI), userInfo: nil, repeats: true)
}
//method to update your UI
func updateUI() {
//update your UI here
//also update your seconds variable value by 1
seconds += 1
if seconds == 60 {
secondTimer?.invalidate()
secondTimer = nil
}
print(seconds)
}
finally call setTimer() wherever you want

getTimeElapsed in Swift with NSTimer

I'm a beginner in Swift and I'm facing a problem.
I want to create a class that contains a timer and a button
Everytime the button is tapped I have to restart the timer, if the time elapsed between the timer starts and the button is tapped is greater (or equal) to 400 milliseconds, I have to call the function in the selector.
But there isn't a "GetTimeElapsed()" method in swift and I don't know how to do it.
If you have some clue/tutorials it could be cool !
Thx guys
Few small steps:
Define the start time: (should happen at the same time you start the timer)
startTime = (NSDate.timeIntervalSinceReferenceDate())
Measure the time difference
let elapsed = NSDate.timeIntervalSinceReferenceDate() - startTime
By default, NSTimer does not have this feature.
Right way is do your job with dates and comparing it.
By the way, you can do a nice extension like this:
public extension NSTimer {
var elapsedTime: NSTimeInterval? {
if let startDate = self.userInfo as? NSDate {
return NSDate().timeIntervalSinceDate(startDate)
}
return nil
}
}
In this way, when you create your timer, you have to set userInfo:
let timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(foo), userInfo: NSDate(), repeats: true)
In your 'foo' method you can test elapsed time:
func foo() {
print(self.timer.elapsedTime)
}

NSTimer not precise enough

I'm setting up timers to execute code once each one finished. However, it seems that the timing of NSTimer is not completely precise, after a while, the timers seem to finish a little too early.
I've set up the following to test the deviation.
(The start button is done in the Storyboard and linked to.)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var startButton: UIButton!
//var mainTimer: NSTimer!
var counter: NSTimeInterval = 0.0
#IBAction func startButtonPressed(sender: AnyObject) {
startMainTimer()
startTimers()
}
func startMainTimer() {
let mainTimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: #selector(ViewController.count), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(mainTimer, forMode: NSRunLoopCommonModes)
}
func count() {
counter += 0.01
}
func startTimers() {
var lastTimeInterval: NSTimeInterval = 0.0
for _ in 0..<50 {
let timeInterval = lastTimeInterval + 1.0
lastTimeInterval = timeInterval
// Not setting up a repeating timer as intervals would be different in my original project where the deviation will be even worse.
let timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: #selector(ViewController.printTime), userInfo: nil, repeats: false)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
}
func printTime() {
print(counter)
}
}
After while, the timers will be early a tenth of a second or even more.
Should I be using NSDate for this to have better timing, or would that be overly complicated? How would the above look with NSDate?
Any help / pointers much appreciated!
It will never be perfect, but you can stop it from compounding by scheduling the next call at the end of the target selector. Calculate a new interval each time based on the current time and when you want it to trigger.
EDIT: I grabbed some code from a very old project to give the idea (that's why it's in Obj-C) -- You want something like this but not exactly:
- (void)constantIntervalTimer {
static const NSTimeInterval secondsBetweenMessageSend = 1.0;
if ( !self.timerShouldStop ) {
NSDate *startTime = [NSDate date];
// your code here, it should take less than a
// second to run or you need to increase the timer interval
// figure out the right time to preserve the interval
NSTimeInterval waitTime = secondsBetweenMessageSend -
[[NSDate date] timeIntervalSinceDate:startTime];
// in case your code runs in more than the interval,
// we just call back immediately
if (waitTime < 0.0)
waitTime = 0.0;
[NSTimer scheduledTimerWithTimeInterval: waitTime
target:self selector:#selector(constantIntervalTimer)
userInfo:nil repeats:NO];
}
}
Once you call this message, it will keep calling itself every second until you set a property called timerShouldStop to YES. You must declare and define this property and set it to NO in your init for this message to work.

Resources