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

How do I create a stopwatch with lap times in swiftUI?

Forums > SwiftUI

Does anyone know how to track laps in a stopwatch with SwiftUI? I already have this code:

class StopWatch: ObservableObject {

    @Published var timeElapsedFormatted = "00:00.00"
    @Published var mode: stopWatchMode = .stopped

    var secondsElapsed = 0.0
    var completedSecondsElapsed = 0.0
    var timer = Timer()

    func start() {
        self.mode = .timing
        timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in
        self.secondsElapsed += 0.01

    func stop() {
        self.mode = .stopped
        self.completedSecondsElapsed = self.secondsElapsed
        self.secondsElapsed = 0.0
        self.timeElapsedFormatted = "00:00.00"

    func pause() {
        self.mode = .paused

    func formatTime() {
        let minutes: Int32 = Int32(self.secondsElapsed/60)
        let minutesString = (minutes < 10) ? "0(minutes)" : "(minutes)"
        let seconds: Int32 = Int32(self.secondsElapsed) - (minutes * 60)
        let secondsString = (seconds < 10) ? "0(seconds)" : "(seconds)"
        let milliseconds: Int32 = Int32(self.secondsElapsed.truncatingRemainder(dividingBy: 1) * 100)
        let millisecondsString = (milliseconds < 10) ? "0(milliseconds)" : "(milliseconds)"
        self.timeElapsedFormatted = minutesString + ":" + secondsString + "." + millisecondsString

enum stopWatchMode {
    case timing
    case stopped
    case paused

But I have a start, pause, stop and laps button. When I click the laps button, the lap time should be saved in a list below the stopwatch. Does anyone know how to solve this?


Without coding it up, I'd think you would use a Date object for your start time, and it would also assign it as the lapStartTime.

When you want a lap split time, create a second Date object as the lapEndTime, do some date math w/ CalendarComponents and output the difference in whatever increment you are looking to use. Also, at this point the lapStartTime would be updated to use the lapEndTime as the end of one split is the start of the next.

On a side note, do you really need to loop your timer so tightly (1/100th of a second)? That would seem to use a lot of cycles, when you could have it update maybe on the 1/10th of a second for showing the running display. When you actually select to stop, you would use that instant in time to create an endTimingDate and do similar date math as used for the lap split timer.

Does this makes sense?


I'm trying to build something similar for my Apple Watch. In my physical therapy I have so many sets of timed holds, 30 seconds each side for example then switch, over say 4 or 6 times. It's very difficult to get my Watch to inform me when the said time is up and I need continue without me struggling to see the watch face or restart a timer.

In essence, I need an interval countdown timer, that also counts the laps. And haptic feedback is essential so my wrist vibrates and the clock resets and keeps counting.

Learning SwiftUI, I am overwhelmed by so many tutorials obsession on fancy font color attributes and animations, those come intuitively to me. What is far more of my interest in learning as is challenging are the concepts introduced by State, Class, Struct, etc. etc... and that is just the beginning (ObservableObject oh my.)

My intention is to keep building and rebuilding this App so the result is very similar (if not slight improved) but with the intention to understand SwiftUI better: build the same thing with more advanced tools- and do the aesthetic some other time.

This is an ugly and currently working version. You choose one of the 4 options of start time, 15, 30, 45 or 60 seconds and the timer starts counting down and counts each loop.

My goal is to introduce @ObservableObject and have two screens but to understand each piece as I go. I may first try to reduce the repetition in my code by using an Array. The difficulty I am currently having is String/Int working consistently for the startCount. In addition, I have yet to add the haptic feedback, the vibration on each arrival at zero.

If anyone would like to help, the most urgent desire I have is to simplify the method that the actual timer counts down. I barely understand how onReceive works, in particular this part perform: { _ in and I think it would all be more clear in my head to make the countdown a func. How would I do that? Anyway, I also may rewrite using enum as you did.

`// // HackingView.swift // InfiniteBuzz WatchKit Extension

import SwiftUI

struct HackingView: View { @State private var lapCount: Int = 0 @State private var startTime: CGFloat = 15 @State private var timeRemaining: CGFloat = 15 @State private var isActive = false

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

//I want code I understand, using a func makes more sense to me to countdown

var body: some View {
    VStack {
        Text("\(Int(timeRemaining)) secs")

        Label("15", systemImage: "clock.arrow.2.circlepath").font(.title3)
            .onTapGesture(perform:  {
                timeRemaining = 15
                isActive = true

        Label("30", systemImage: "clock.arrow.2.circlepath").font(.title3)
            .onTapGesture(perform:  {
                timeRemaining = 30
                isActive = true

        Label("45", systemImage: "clock.arrow.2.circlepath").font(.title3)
            .onTapGesture(perform:  {
                timeRemaining = 45
                isActive = true
        Label("60", systemImage: "clock.arrow.2.circlepath").font(.title3)
            .onTapGesture(perform:  {
                timeRemaining = 60
                isActive = true
        Text("laps \(lapCount)")

                perform: { _ in
        if timeRemaining > 0  {
            timeRemaining -= 1
        } else {
            lapCount += 1
            timeRemaining = startTime


struct HackingView_Previews: PreviewProvider {
    static var previews: some View {


I can source the actual links if necessary but thanks to rebeloper, swiftful thinking and twostraws for the code copy and paste.


Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.