Persevering with my research on SwiftUI, I’m growing a stopwatch. However I discovered a bug that I couldn’t remedy.
Steps to breed the error:
- Begin the stopwatch (stopwatch is operating)
- Click on the lap button a couple of instances (to fill the laps listing)
- Scroll by means of the listing (up or down).
The BUG is right here: the stopwatch pauses, I cease the scroll and the time begins once more
Observe the code:
import SwiftUI
struct StopwatchView: View {
@State personal var timer: Timer? = nil
@State personal var timeElapsed: TimeInterval = 0
@State personal var isRunning: Bool = false
@State personal var lapTimes: [TimeInterval] = []
@State personal var lastLapTime: TimeInterval? = nil
personal let timeFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.minute, .second]
formatter.zeroFormattingBehavior = [.pad]
return formatter
}()
personal let millisecondsFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
formatter.minimumIntegerDigits = 2
return formatter
}()
var physique: some View {
VStack {
Textual content("(formattedTime(timeElapsed))")
.font(.system(dimension: 90, weight: .gentle))
.padding(.horizontal, 10)
.padding(.prime, 50)
HStack {
VStack(alignment: .main) {
Textual content("Quickest")
.font(.headline)
.foregroundColor(.inexperienced)
if let minLap = lapTimes.min() {
Textual content(formattedTime(minLap))
.foregroundColor(.white)
.padding(10)
.background(Shade.inexperienced)
.cornerRadius(5)
}
}
Spacer()
VStack(alignment: .trailing) {
Textual content("Slowest")
.font(.headline)
.foregroundColor(.pink)
if let maxLap = lapTimes.max() {
Textual content(formattedTime(maxLap))
.foregroundColor(.white)
.padding(10)
.background(Shade.pink)
.cornerRadius(5)
}
}
}
.padding(.horizontal, 30)
.padding(.backside, 20)
Listing {
ForEach(lapTimes.indices, id: .self) { index in
HStack {
Textual content("Lap (lapTimes.rely - index)")
Spacer()
Textual content(formattedTime(lapTimes[index]))
.foregroundColor(colorForTime(at: index))
}
}
}
.listStyle(PlainListStyle())
.padding(.backside, 30)
.onChange(of: lapTimes) { _ in
scrollToTop()
}
Spacer()
HStack(spacing: 30) {
Button(motion: {
if isRunning {
stopTimer()
} else {
startTimer()
}
}) {
Textual content(isRunning ? "Cease" : "Begin")
.foregroundColor(isRunning ? .pink : .inexperienced)
.font(.system(dimension: 24, weight: .daring))
.body(width: 120, top: 120)
.background(Circle().fill(Shade.black.opacity(0.1)))
}
Button(motion: {
if isRunning {
addLap()
} else {
resetTimer()
}
}) {
Textual content(isRunning ? "Lap" : "Reset")
.foregroundColor(.black)
.font(.system(dimension: 24, weight: .daring))
.body(width: 120, top: 120)
.background(Circle().fill(Shade.black.opacity(0.1)))
}
}
.padding(.backside, 50)
}
}
personal func colorForTime(at index: Int) -> Shade {
guard !lapTimes.isEmpty else { return .black }
let maxTime = lapTimes.max() ?? 0
let minTime = lapTimes.min() ?? 0
if lapTimes[index] == maxTime {
return .pink
} else if lapTimes[index] == minTime {
return .inexperienced
} else {
return .black
}
}
personal func formattedTime(_ time: TimeInterval) -> String {
let minutesAndSeconds = timeFormatter.string(from: time) ?? "00:00"
let milliseconds = Int((time.truncatingRemainder(dividingBy: 1)) * 100)
return "(minutesAndSeconds):(String(format: "%02d", milliseconds))"
}
personal func startTimer() {
isRunning = true
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
timeElapsed += 0.01
}
}
personal func stopTimer() {
isRunning = false
timer?.invalidate()
timer = nil
}
personal func resetTimer() {
timeElapsed = 0
lapTimes.removeAll()
lastLapTime = nil
}
personal func addLap() {
if let lastLap = lastLapTime {
let lapDuration = timeElapsed - lastLap
lapTimes.insert(lapDuration, at: 0)
} else {
lapTimes.insert(timeElapsed, at: 0)
}
lastLapTime = timeElapsed
}
personal func scrollToTop() {
DispatchQueue.major.async {
if let scrollView = UIApplication.shared.home windows.first?.rootViewController?.view.subviews.first(the place: { $0 is UIScrollView }) as? UIScrollView {
scrollView.setContentOffset(.zero, animated: true)
}
}
}
}
#Preview {
StopwatchView()
}
This code is in its preliminary part, I’ll modularize it, create views for every part to divide the tasks… however for now I need assistance to resolve the battle between the scroll and the timer.