I am currently working on an app, and it has a timer in a separate view from the main ContentView(). I have realized that I am not resetting the timer each time, and since it is initialized with a binding var, the var is increasing faster than desired (add one to var every 0.01 seconds). Simply setting the var to a binding and changing it that way hasn't worked for some reason, and I have created a function that runs every time the timer fires to check if the isRunning binding var is false, and if it is, invalidate the timer. The code for ContentView is:
import Foundation
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(entity: Time.entity(), sortDescriptors: [])
private var times: FetchedResults<Time>
@State var startstop: String = "Start"
@State var start: Bool = false
@State var elapsedTime: Int = 0
@State var timeMinutes: Int = 0
@State var timeSeconds: Int = 0
@State var timeMilliseconds: Int = 0
@State var hour: Int = 0
@State var minute: Int = 0
@State var second: Int = 0
@State var formattedTime: String = ""
@State var saveTime: Int = 0
@State var timerRunning: Bool = true
@State var firstRun: Bool = true
// Format date after core data fetch to allow creating NSSortDescript for date
var body: some View {
VStack{
Text("Cube App")
.font(.largeTitle)
.fontWeight(.bold)
.padding(.top, 4.0)
Text(scrambles.randomElement()!)
.font(.title)
.fontWeight(.semibold)
.multilineTextAlignment(.center)
.padding([.top, .leading, .trailing])
if start{
Stopwatch(progressTime: $elapsedTime, isRunning: $timerRunning)
.padding(.top)
.fontWeight(.bold)
.onAppear{
timerRunning = true
toggleStartStop()
}
}
Button(startstop){
toggleStart()
if start == false{
saveTime = elapsedTime
stopTime()
elapsedTime = 0
toggleStartStop()
}
}
.fontWeight(.bold)
.font(.system(size: 30))
.padding(.top, 30)
Spacer()
}
}
private func stopTime(){
timerRunning = false
print(saveTime)
addTime()
}
private func addTime(){
withAnimation{
timeMinutes = saveTime / 6000
timeSeconds = (saveTime % 6000) / 100
timeMilliseconds = saveTime % 100
let time = Time(context: viewContext)
if timeMinutes == 0{
if timeMilliseconds < 10{
time.time = "\(timeSeconds).0\(timeMilliseconds)"
}else{
time.time = "\(timeSeconds).\(timeMilliseconds)"
}
}else if timeMilliseconds < 10{
if timeSeconds < 10{
time.time = "\(timeMinutes):0\(timeSeconds).0\(timeMilliseconds)"
}else{
time.time = "\(timeMinutes):\(timeSeconds).0\(timeMilliseconds)"
}
}else{
if timeSeconds < 10{
time.time = "\(timeMinutes):0\(timeSeconds).\(timeMilliseconds)"
}else{
time.time = "\(timeMinutes):\(timeSeconds).\(timeMilliseconds)"
}
}
time.date = Date()
saveContext()
}
}
private func saveContext(){
do {
try viewContext.save()
} catch {
let error = error as NSError
print(error)
fatalError(error as! String)
}
}
private func toggleStart(){
if start{
start = false
}else{
start = true
}
}
private func toggleStartStop(){
if startstop == "Start"{
startstop = "Stop"
}else{
startstop = "Start"
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let persistenceController = PersistenceController.shared
ContentView().environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
Note: I have excluded the array of "scrambles" as it went over the character limit, but it is only used in a text in the vstack. The code for the Stopwatch (Timer) file is:
import SwiftUI
struct Stopwatch: View {
@Binding var progressTime: Int
@Binding var isRunning: Bool
/// Computed properties to get the progressTime in hh:mm:ss format
var hours: Int {
progressTime / 6000
}
var minutes: Int {
(progressTime % 6000) / 100
}
var seconds: Int {
progressTime % 100
}
var timer: Timer {
Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) {_ in
progressTime += 1
reset()
}
}
var body: some View {
HStack(spacing: 2) {
StopwatchUnitView(timeUnit: hours)
Text(":")
StopwatchUnitView(timeUnit: minutes)
Text(".")
StopwatchUnitView(timeUnit: seconds)
}.onAppear(perform: { _ = timer })
}
func reset(){
if isRunning == false{
self.timer.invalidate()
print("invailidated")
}
}
func toggleRun(){
if isRunning{
isRunning = false
progressTime = 0
print(isRunning)
timer.invalidate()
}else{
isRunning = true
}
}
func fetchTime() -> Int{
return progressTime
}
}
struct StopwatchUnitView: View {
var timeUnit: Int
/// Time unit expressed as String.
/// - Includes "0" as prefix if this is less than 10
var timeUnitStr: String {
let timeUnitStr = String(timeUnit)
return timeUnit < 10 ? "0" + timeUnitStr : timeUnitStr
}
var body: some View {
HStack (spacing: 2) {
Text(timeUnitStr.substring(index: 0)).frame(width: 10)
Text(timeUnitStr.substring(index: 1)).frame(width: 10)
}
}
}
extension String {
func substring(index: Int) -> String {
let arrayString = Array(self)
return String(arrayString[index])
}
}
struct stopwatch_Previews: PreviewProvider {
static var previews: some View {
Stopwatch(progressTime:.constant(0), isRunning: .constant(true) /*firstRun: .constant(true)*/)
}
}
Could somebody help me reset the timer each time and fix the issue?