UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Basing a StopWatch off of Date()

Forums > SwiftUI

I am wanting to have a stopwatch in my app that runs completely off the device's time. I have my code below which takes the time in which the start button is pressed, and then every second updates the secondsElapsed to be the difference between the startTime and current. I am getting stuck on implementing a pause function. If I just invalidate the update timer, then the timer will restart having pretty much carried on from where it left off. Any ideas on how this could be done?

  class StopWatchManager: ObservableObject{

  @Published var secondsElapsed = 0

  var startTime: Date = Date()

  var timer = Timer()

  func startWatch(){
      timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ timer in
          let current = Date()
          let diffComponents = Calendar.current.dateComponents([.second], from: self.startTime, to: current)
          let seconds = (diffComponents.second ?? 0)
          self.secondsElapsed = seconds

      }

  }

  func pauseWatch(){
      timer.invalidate()
  }

}

I display the stopwatch using this code below:

struct ContentView: View {

    @ObservedObject var stopWatchManager = StopWatchManager()

    var body: some View{
        HStack{
            Button("Start"){
                stopWatchManager.startWatch()
            }
            Text("\(stopWatchManager.secondsElapsed)")
            Button("Pause"){
                stopWatchManager.pauseWatch()
            }
        }
    }
}

3      

One way is to measure changes since the last time the timer ran rather than time elapsed since the StopWatchManager was created. Here I've three changes to your code:

  1. Made seconds_elapsed a Double and used more precision in the diffComponents
  2. Changed the updated of secondsElapsed to add the time delta
  3. Set the startTime to the current time at the end of the timer closure so each time it's called you are just measuring the time delta since the last call.

I'm sure there are other ways to do it, as well.

import SwiftUI

class StopWatchManager: ObservableObject{

    @Published var secondsElapsed = 0.0

    var startTime: Date = Date()

    var timer = Timer()

    func startWatch(){
        startTime = Date()
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ timer in
            let current = Date()
            let diffComponents = Calendar.current.dateComponents([.second, .nanosecond], from: self.startTime, to: current)
            let seconds = Double(diffComponents.second ?? 0) + Double(diffComponents.nanosecond ?? 0) / 1_000_000_000
            self.secondsElapsed += seconds
            self.startTime = current
        }

    }

    func pauseWatch(){
        timer.invalidate()
    }
}

struct ContentView: View {

    @ObservedObject var stopWatchManager = StopWatchManager()

    var body: some View{
        HStack{
            Button("Start"){
                stopWatchManager.startWatch()
            }
            Text("\(stopWatchManager.secondsElapsed, specifier: "%.0f")")
            Button("Pause"){
                stopWatchManager.pauseWatch()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.