A SwiftUI Stopwatch App with Lap Timing
In this tutorial, we’ll create a sophisticated stopwatch app called “LapTrack” using SwiftUI. LapTrack will feature a sleek interface with start/stop functionality, lap timing, and a list view of recorded laps.
Setting Up the Project
-
Open Xcode and create a new SwiftUI project.
-
Name your project “LapTrack”.
-
Replace the contents of your
ContentView.swift
file with the following code:
import SwiftUI
struct ContentView: View {
@State private var elapsedTime: TimeInterval = 0
@State private var timer: Timer?
@State private var isRunning = false
@State private var lapTimes: [TimeInterval] = []
var body: some View {
VStack {
Text("LapTrack")
.font(.largeTitle)
.fontWeight(.bold)
.padding(.top)
Text(timeString(from: elapsedTime))
.font(.system(size: 60, weight: .thin, design: .monospaced))
.padding()
HStack(spacing: 20) {
Button(action: {
if isRunning {
stopTimer()
} else {
startTimer()
}
}) {
Text(isRunning ? "Stop" : "Start")
.font(.title2)
.foregroundColor(.white)
.frame(width: 100, height: 50)
.background(isRunning ? Color.red : Color.green)
.cornerRadius(10)
}
Button(action: recordLapTime) {
Text("Lap")
.font(.title2)
.foregroundColor(.white)
.frame(width: 100, height: 50)
.background(Color.blue)
.cornerRadius(10)
}
.disabled(!isRunning)
Button(action: resetTimer) {
Text("Reset")
.font(.title2)
.foregroundColor(.white)
.frame(width: 100, height: 50)
.background(Color.gray)
.cornerRadius(10)
}
}
.padding()
List {
ForEach(lapTimes.indices, id: \.self) { index in
HStack {
Text("Lap \(lapTimes.count - index)")
.font(.headline)
Spacer()
Text(timeString(from: lapTimes[index]))
.font(.system(.body, design: .monospaced))
}
}
}
}
}
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
elapsedTime += 0.01
}
isRunning = true
}
func stopTimer() {
timer?.invalidate()
timer = nil
isRunning = false
}
func resetTimer() {
stopTimer()
elapsedTime = 0
lapTimes.removeAll()
}
func recordLapTime() {
lapTimes.insert(elapsedTime, at: 0)
}
func timeString(from timeInterval: TimeInterval) -> String {
let minutes = Int(timeInterval) / 60
let seconds = Int(timeInterval) % 60
let hundredths = Int((timeInterval.truncatingRemainder(dividingBy: 1)) * 100)
return String(format: "%02d:%02d.%02d", minutes, seconds, hundredths)
}
}
Understanding the Code
Let’s break down the key components of our LapTrack app:
State Variables
-
elapsedTime
: Stores the current time of the stopwatch. -
timer
: An optional Timer object to manage the stopwatch updates. -
isRunning
: A boolean to track whether the stopwatch is running or not. -
lapTimes
: An array to store recorded lap times.
User Interface
The UI is structured using a VStack
containing:
-
App title
-
Large time display
-
Control buttons (Start/Stop, Lap, Reset)
-
List of recorded lap times
Key Functions
-
startTimer()
: Initializes a Timer that updateselapsedTime
every 0.01 seconds. -
stopTimer()
: Stops the timer and updates the UI state. -
resetTimer()
: Resets the stopwatch and clears lap times. -
recordLapTime()
: Adds the current elapsed time to the lap times list. -
timeString(from:)
: Formats a TimeInterval into a readable string (MM:SS.hh).
Conclusion
This project demonstrates several important concepts in iOS development:
-
State management in SwiftUI
-
Working with timers and time intervals
-
Creating a responsive and intuitive user interface
-
Handling user input and updating the UI accordingly
-
Managing and displaying lists of dynamic data