NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

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

1      

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

1      

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

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.