benpomeroy9
benpomeroy9

Reputation: 417

Basing a StopWatch off of Date() - 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()
            }
        }
    }
}

Upvotes: 0

Views: 396

Answers (1)

vacawama
vacawama

Reputation: 154533

Yes. Here is how to do it:

  1. When pause is pressed, note the current time and compute the elapsed time for the timer. Invalidate the update timer.
  2. When the timer is resumed, take the current time and subtract the elapsed time. Make that the startTime and restart the update timer.

Here's the updated code:

class StopWatchManager: ObservableObject{
    
    @Published var secondsElapsed = 0
    
    var startTime: Date = Date()
    var elapsedTime = 0.0
    var paused = false
    var running = false
    
    var timer = Timer()
    
    func startWatch(){
        guard !running else { return }
        
        if paused {
            startTime = Date().addingTimeInterval(-elapsedTime)
        } else {
            startTime = Date()
        }
        paused = false
        running = true
        
        timer = Timer.scheduledTimer(withTimeInterval: 0.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(){
        guard !paused else { return }
        
        timer.invalidate()
        elapsedTime = Date().timeIntervalSince(startTime)
        paused = true
        running = false
    }
    
}

Things to note:

  • I changed the timer interval to 0.1 from 1 to avoid missing updates.
  • I added paused and running state variables to keep the buttons from being pressed more than once in a row.

Upvotes: 2

Related Questions