I am creating a lap stopwatch, and I am having trouble stopping the timer when the "STOP" button is clicked. I am able to get the text to reset to 0, but the timer keeps running and if I hit start again the timer is running as if I never hit stop.
Timer code:
func goTimer()
{
if timer == nil {
timer = Timer.scheduledTimer(timeInterval : 0.1,
target : self,
selector :#selector(timerAction(_:)),
userInfo: nil, repeats: true);
}
}
func stopTimer()
{
if timer != nil {
timer!.invalidate()
timer = nil
}
}
func updateTimer() {
let intervalTotal = -Int(startDate.timeIntervalSinceNow)
let hours = intervalTotal / 3600
let minutes = intervalTotal / 60 % 60
let seconds = intervalTotal % 60
if startButton.currentTitle == "STOP" {
totalTime.text = String(format: "%02i:%02i:%02i", hours, minutes, seconds)
lapTime.text = String(format: "%02i:%02i:%02i", hours, minutes, seconds)
}
}
#objc func timerAction (_ timer : Timer) {
print("timerAction(_:)")
self.updateTimer()
}
#IBAction func startTimer(_ sender: Any) {
if startButton.currentTitle == "STOP" {
startButton.backgroundColor = UIColor.systemGreen
startButton.setTitle("START", for : .normal)
stopTimer()
lapTime.text = "00:00:00"
}
else if startButton.currentTitle == "START" {
startButton.backgroundColor = UIColor.systemRed
startButton.setTitle("STOP", for : .normal)
goTimer()
lapButton.isEnabled = true
lapButton.backgroundColor = UIColor.systemBlue
}
}
let intervalTotal = -Int(startDate.timeIntervalSinceNow)
I don't see you resetting that startDate in your stopTimer() function.
you have a variable
var startDate: Date = Date()
somewhere on top of your code. If its not a variable (if its let) change it to variable. You want to set this value when you start your timer. so
func goTimer() {
// Add this to set the date to when your function starts
startDate = Date()
....
}
That's because when you're setting the value in your label here
func updateTimer() {
let intervalTotal = -Int(startDate.timeIntervalSinceNow)
....
}
you're setting the value from startDate until now
Related
I developing a timer app for an apple watch.
I have two different Views at the moment. One with the actual timer (TimerController) and another with a pause-button (SwipeController).
I'm trying to stop/start the timer in the TimerController with the action from the button in the SwipeController.
Problem is that the timer stops, but the timer will not start again after hitting the button the second time.
If I press the button one time, the timer stops. If i press it again two times the timer will start again but will not stop when hitting the button again.
Any ideas of what the problem could be?
TimeController
import WatchKit
import Foundation
import UserNotifications
class TimerController: WKInterfaceController {
#IBOutlet weak var timerOutlet: WKInterfaceTimer! //
#IBOutlet weak var simple_timer_label: WKInterfaceLabel!
var myTimer : Timer?
var duration : TimeInterval = 1 //arbitrary number. 1 seconds
var isPaused = false //flag to determine if it is paused or not
var elapsedTime : TimeInterval = 0.0 //time that has passed between
var number_as_a_timer:Int = 0
var startTime = NSDate()
var dim_date = Date()
var current_minute: Int = 0
var current_hour: Int = 0
var curent_second: Int = 0
var seperate_is_paused_bool: Bool = false
override func awake(withContext context: Any?) {
super.awake(withContext: context)
start_timer()
}
func timeString(time:TimeInterval) -> String {
let hours: Int = Int(time) / 3600
let minutes: Int = Int(time) / 60 % 60
let seconds: Int = Int(time) % 60
let com = NSDateComponents()
com.minute = minutes
com.second = seconds
com.hour = hours
dim_date = NSCalendar.current.date(from: com as
DateComponents)!
self.timerOutlet.setDate(dim_date)
self.timerOutlet.start()
return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
func start_timer() {
myTimer = Timer.scheduledTimer(timeInterval: duration, target:
self,selector: #selector(timerDone), userInfo: nil, repeats:
true)
}
#objc private func timerDone(){
//timer done counting down
if !isPaused {
number_as_a_timer += 1
let output:String = self.timeString(time:
TimeInterval(number_as_a_timer))
self.simple_timer_label.setText(output)
print(output)
}
}
override func willActivate() {
super.willActivate()
NotificationCenter.default.addObserver(self, selector:
#selector(stop_timer(notification:)), name: .stopTimer, object:
nil)
}
#objc func stop_timer(notification:NSNotification) {
// Timer is paused. so unpause it and resume countdown
if isPaused {
myTimer = Timer.scheduledTimer(timeInterval: 1,
target:self, selector: #selector(timerDone), userInfo: nil,
repeats: true)
self.isPaused = false
print("timer paused: resumming1")
} else {
isPaused = true
print("stoping timer")
//get how much time has passed before they paused it
let paused = NSDate()
elapsedTime += paused.timeIntervalSince(startTime as Date)
//stop watchkit timer on the screen
timerOutlet.stop()
//stop the ticking of the internal timer
myTimer!.invalidate()
}
}
}
extension Notification.Name {
static let stopTimer = Notification.Name("stopTimer")
}
SwipeController
import WatchKit
import Foundation
import UserNotifications
class SwipeController: WKInterfaceController {
//#IBOutlet weak var myTimer: WKInterfaceTimer!
var timer = TimerController()
var status: Bool = false
override func awake(withContext context: Any?) {
super.awake(withContext: context)
}
#IBAction func PauseButton() {
if timer.myTimer == nil {
print("timer is nil or invalidated")
print("Y: \(timer.isPaused)")
let userInfo = ["stop": true] as [String: Bool] // you
could also transfer data
NotificationCenter.default.post(name: .stopTimer, object:
nil, userInfo: userInfo)
} else {
print("empty block")
}
}
}
it looks like you aren't ever actually checking for you isPaused boolean to be true or false in your if statement when checking if your timer is paused.
if isPaused { <-----------
myTimer = Timer.scheduledTimer(timeInterval: 1,
target:self, selector: #selector(timerDone), userInfo: nil,
repeats: true)
self.isPaused = false
print("timer paused: resumming1")
I am trying to calculate the time it takes a vehicle to go between two checkpoints. I have setup the following timer to achieve this.
func startTimer() {
if hasStarted == true && timerStarted == false {
print("Timing started!")
gameTimer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: (#selector(activeTiming)), userInfo: nil, repeats: true)
timerStarted = true
}
}
#objc func activeTiming() {
print("Active timing block")
if(hasFinished == false) {
gameTime = gameTime + 0.001
print("Add time succeeded")
} else {
gameTimer?.invalidate()
}
}
The expected output would be the following:
Timing started!
Active timing block
Add time succeeded
Add time succeeded ... etc
The actual output:
Timing started!
So it would appear that the startTimer is properly being called but the timer is not firing the activeTiming block of code. Any suggestions would be extremely helpful. Thank you for your help in advance.
Posting this code as it is what I'm using, but I’m no expert on Swift, so your mileage may vary!
class PerformanceTest {
var name: String = ""
var tolerance: Int64 = 0
var lastTime: Int64 = 0
var thisTime: Int64 = 0
var delta: Int64 = 0
var percent: Float = 0
func setTolerance(vName: String, vTolerance: Int64) {
name = vName
tolerance = vTolerance
}
func reset() {
delta = 0
percent = Float((Float(delta) / Float(tolerance))) * 100
//high = 0
}
func start() {
lastTime = Date().toMillis()
}
func finish() {
thisTime = Date().toMillis()
let vDelta = thisTime - lastTime
if(vDelta > delta) {
delta = vDelta
percent = Float((Float(delta) / Float(tolerance))) * 100
if(delta > tolerance) {
print("Performance Indicator: \(name) Above Tolerance" + String(format: "%3.0f", percent) + "%")
}
}
}
func display() -> String {
//high = delta
//print("\(vString) Tolerance: \(tolerance) Max: \(high)")
return String(format: "%3.0f", percent) + "% |"
}
}
extension Date {
func toMillis() -> Int64! {
return Int64(self.timeIntervalSince1970 * 1000)
}
Usage:
var performanceDefenseLoop = PerformanceTest()
performanceDefenseLoop.setTolerance(vName: "DefenseLoop", vTolerance: 150)
func timeToUpdateDefenses()
{
performanceDefenseLoop.start()
defesensesLoop()
performanceDefenseLoop.finish()
print("\(performanceDefenseLoop.Display())"
}
// To reset
performanceDefenseLoop.reset()
Be sure to start timers in the main thread!
I also faced a similar issue where the timer body was not getting called. I found out that the timer was getting scheduled from a background thread instead of the main thread. Wrapping it in DispatchQueue.main.async fixed it.
DispatchQueue.main.async {
self.pingTimer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true, block: { [weak self] timer in
//
Logger.shared.log(.anyCable, .debug, "timer run")
self?.sendPing(sender: self)
})
}
Your code works for me, but that isn't how you want to time an event because the Timer isn't that accurate, and you are wasting a lot of computing time (read battery).
Instead, I suggest the following approach:
When the event starts, record the start time:
let startTime = Date()
When the event ends, compute the elapsed time:
let elapsedTime = Date().timeIntervalSince(startTime)
elapsedTime will be in seconds (including fractional seconds).
I want to stop this timer and then restart it from where I stopped it.
secondsTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(addSeconds), userInfo: nil, repeats: true)
Below, it was suggested I shouldn't increment a timer in my timer handler. Why not?
For example, using GCD timer:
func countSeconds() {
secondsTimer = DispatchSource.makeTimerSource(queue: .main)
secondsTimer?.schedule(deadline: .now(), repeating: 1.0)
secondsTimer?.setEventHandler { [weak self] in
self?.addSeconds()
}
}
#objc func addSeconds() {
seconds += 1
}
func startGame() {
secondsTimer?.resume()
}
We don't pause/resume Timer instances. We stop them with invalidate(). And when you want to restart it, just create new timer.
Please refer to the Timer documentation, also available right in Xcode.
Note that you can suspend and resume GCD timers, DispatchSourceTimer.
var timer: DispatchSourceTimer? // note, unlike `Timer`, we have to maintain strong reference to GCD timer sources
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 1.0)
timer?.setEventHandler { [weak self] in // assuming you're referencing `self` in here, use `weak` to avoid strong reference cycles
// do something
}
// note, timer is not yet started; you have to call `timer?.resume()`
}
func startTimer() {
timer?.resume()
}
func pauseTiemr() {
timer?.suspend()
}
func stopTimer() {
timer?.cancel()
timer = nil
}
Please note, I am not suggesting that if you want suspend and resume that you should use GCD DispatchSourceTimer. Calling invalidate and recreating Timer as needed is simple enough, so just do that. I only provide this GCD information for the sake of completeness.
By the way, as a general principle, never "increment" some counter in your timer handler. That's a common mistake. Timers are not guaranteed to fire every time or with exact precision. Always save some reference time at the start, and then in your event handler, calculate differences between the current time and the start time. For example, extending my GCD timer example:
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 0.1)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second, .nanosecond]
formatter.zeroFormattingBehavior = .pad
timer?.setEventHandler { [weak self] in
guard let start = self?.start else { return }
let elapsed = (self?.totalElapsed ?? 0) + CACurrentMediaTime() - start
self?.label.text = formatter.string(from: elapsed)
}
}
var start: CFTimeInterval? // if nil, timer not running
var totalElapsed: CFTimeInterval?
#objc func didTapButton(_ button: UIButton) {
if start == nil {
startTimer()
} else {
pauseTimer()
}
}
private func startTimer() {
start = CACurrentMediaTime()
timer?.resume()
}
private func pauseTimer() {
timer?.suspend()
totalElapsed = (totalElapsed ?? 0) + (CACurrentMediaTime() - start!)
start = nil
}
I do it with this code:
var timer: Timer?
func startTimer() {
timer = .scheduledTimer(withTimeInterval: 4, repeats: false, block: { _ in
// Do whatever
})
}
func resetTimer() {
timer?.invalidate()
startTimer()
}
You can start, stop and reset timer in swift4
class ViewController: UIViewController {
var counter = 0
var timer = Timer()
var totalSecond = 20
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func start_btn(_ sender: Any) {
timer.invalidate() // just in case this button is tapped multiple times
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
#IBAction func stop_btn(_ sender: Any) {
do {
self.timer.invalidate()
}
func timeFormatted(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
return String(format: "0:%02d", seconds)
}
}
#IBAction func reset_btn(_ sender: Any) {
timer.invalidate()
//timerAction()
counter = 0
label1.text = "\(counter)"
}
#objc func timerAction()
{
counter += 1
label1.text = "\(counter)"
}
}
You can declare the Timer as 'weak var' instead of just 'var' like:
weak var timer: Timer?
Now you can pause your timer with:
timer?.invalidate()
To resume:
timer?.fire()
I am trying to loop a timer with a specific number of counting...
eg... 1, 2, 3, 4, 5
then loop again, the same counting, 10 times, then stop.
I was able to do the counting, but I can't do the looping of the counting.
What am I doing wrong?
var times = 0
var stopCounting = 10
#IBAction func buttonPressed(sender: UIButton) {
self.startTimer()
}
func startTimer(){
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("startCounting"), userInfo: nil, repeats: true)
}
func startCounting (){
times += 1
if times < stopCounting + 1{
if counter > -1 && counter < 6 {
counting.text = String(counter++)
} else if counter == Int(counting.text) {
counting.text = "0"
}
}
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var strTime: UILabel!
var timer = NSTimer()
var endTime: NSTimeInterval = 0
var now: NSTimeInterval { return NSDate().timeIntervalSinceReferenceDate }
var times = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func updateText(){
let time = Int(ceil(endTime - now))
if time == 0 {
times++
if times == 10 {
strTime.text = "end"
timer.invalidate()
}
endTime = NSDate().timeIntervalSinceReferenceDate + 5
} else {
strTime.text = time.description
}
}
#IBAction func buttonPressed(sender: UIButton) {
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "updateText", userInfo: nil, repeats: true)
endTime = NSDate().timeIntervalSinceReferenceDate + 5
sender.hidden = true
}
}
to make it count from 1 to 5 you need to change from ceil to floor, abs() and add +1
func updateText(){
let time = Int(floor(abs(endTime - now - 5)))
if time == 5 {
times++
if times == 10 {
strTime.text = "10" + " " + "0"
timer.invalidate()
}
endTime = NSDate().timeIntervalSinceReferenceDate + 5
} else {
strTime.text = times.description + " " + (time+1).description
}
}
Never done swift, but took a stab in the dark, why not.
var loops = 5 // Number of times to loop the counting
var countTo = 10 // Number to count to
var loopCount = 0 // Number of loops
#IBAction func buttonPressed(sender: UIButton) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: Selector("startCounting"),
userInfo: nil,
repeats: true)
}
func startCounting (){
// Loop from 1 to countTo, setting the text for each number
for i in 1..countTo {
counting.text = String(i)
}
// Done counting, reset to zero
counting.text = "0"
// Update the number of times the counting has run
loopCount++
// If have completed all the loops, invalidate the timer
if (loopCounter >= loops) {
self.timer.invalidate()
}
}
My question is based on a time interval
I'm getting the hour and minutes (NOW) and I want to create a timer for 75 minutes since that moment but I want the timer to continue working even if i close the app even on multitasking
So i was thinking if I save the time as a NSUserDefault value and every time I open the app It reads the stored value and recalculates
This is my actual code
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(30, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
//here I should update the GUI with the remaining minutes and do a NSUserDefault check if the 75 minutes have already
}
#IBAction func save_time(sender: AnyObject) {
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
var hour = components.hour
var minutes = components.minute + 75
if (minutes>59){
hour += 1
minutes -= 60
}
defs.setInteger(hour, forKey: "u_hora")
defs.setInteger(minutes, forKey: "u_minutos")
}
You can just store them as TimeInterval using NSUserDefaults and you should create a time interval extension to format your time string as desired:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var strTimer: UILabel!
var tasksManager = NSTimer()
var endTime:NSTimeInterval = 0
var now: NSTimeInterval {
return NSDate().timeIntervalSinceReferenceDate
}
func updateTime(){
strTimer.text = ( endTime - now ).time
}
override func viewDidLoad() {
super.viewDidLoad()
// NSUserDefaults().removeObjectForKey("endTime")
// loads endTime if it exists otherwise assign 0 value
endTime = NSUserDefaults().valueForKey("endTime") as? NSTimeInterval ?? 0
// restart timer if endTime exists
if endTime > 0 {
tasksManager = NSTimer.scheduledTimerWithTimeInterval(1/15, target: self, selector: "updateTime", userInfo: nil, repeats:true)
} else {
strTimer.text = "0:01:00.00"
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func startTimer (sender: AnyObject) {
// sets endTime new timeinterval to a future point in time
// so we need to add n seconds from now
endTime = now + 60.0 // now + n seconds
// saves it using NSUserDefaults
NSUserDefaults().setValue(endTime, forKey: "endTime")
// if the timer doest exists we create one
if !tasksManager.valid {
tasksManager = NSTimer.scheduledTimerWithTimeInterval(1/30, target: self, selector: "updateTime", userInfo: nil, repeats:true)
// if the timer exists we invalidate it
} else {
tasksManager.invalidate()
endTime = 0
strTimer.text = "0:01:00.00"
NSUserDefaults().removeObjectForKey("endTime")
}
}
}
extension NSTimeInterval {
var time: String {
return String(format: "%d:%02d:%02d.%02d", Int(self/3600), Int(self/60%60), Int(self%60), Int(self*100%100))
}
}
You can save the current date and time in NSUserDefaults like this.
NSUserDefaults.standardUserDefaults().setObject(NSDate(), forKey: "dateKey")
You can calculate the number of minutes elapsed between saved time and current time in this way.
if let savedDate = NSUserDefaults.standardUserDefaults().objectForKey("dateKey") as? NSDate
{
let currentDate = NSDate()
let distanceBetweenDates = currentDate.timeIntervalSinceDate(savedDate)
let secondsInAnMinute = 60.0;
let minutesElapsed = distanceBetweenDates / secondsInAnMinute;
println(minutesElapsed)
println(distanceBetweenDates)
}