SwiftUI Multiple Timer Management - swift

I'm making the app needs to control several timer at once, and now facing 2 problems.
1: N timers keep running N times faster than expected.
2: Stop button not working for each timer.
2nd problem is the core problem that I'm running into the real project, and would like to understand how to manage the multiple timer instances when they are generated with loop structure.
I pasted the test code, and this code does not use TimelineView for a reason, understanding how the Timer works.
import SwiftUI
import Combine
import Foundation
struct ContentView: View {
#StateObject var timerData = TimerDataViewModel()
var body: some View {
ScrollView{
ForEach(0..<10) { _ in
CurrentDateView(timerData: timerData)
Button(action:{
timerData.stop()
}, label:{
Text("STOP THIS TIMER")
})
}
}
}
}
struct CurrentDateView: View {
#State private var currentDate = Date()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#ObservedObject var timerData: TimerDataViewModel
var body: some View {
Text("\(Int(timerData.hoursElapsed), specifier: "%02d"):\(Int(timerData.minutesElapsed), specifier: "%02d"):\(Int(timerData.secondsElapsed), specifier: "%02d")")
.fontWeight(.bold)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onAppear(){
timerData.start()
}
}
}
import Foundation
import SwiftUI
class TimerDataViewModel: ObservableObject{
#Published var timer = Timer()
#Published var startTime : Double = 0.0
#Published var secondsOriginal = 0.0
#Published var secondsElapsed = 0.0
#Published var secondsElapsed_ = 0.0
#Published var minutesElapsed = 0.0
#Published var hoursElapsed = 0.0
enum stopWatchMode {
case running
case stopped
case paused
}
init(){
// start()
print("initialized")
}
func start(){
self.secondsOriginal = self.startTime
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ timer in
self.secondsOriginal += 1
self.secondsElapsed_ = Double(Int(self.secondsOriginal))
self.secondsElapsed = Double(Int(self.secondsOriginal)%60)
self.minutesElapsed = Double(Int(self.secondsOriginal)/60 % 60)
self.hoursElapsed = Double(Int(self.secondsOriginal)/3600 % 24)
}
}
func stop(){
self.timer.invalidate()
}
}

Related

Timer within EnvironmentObject view model not updating the View

I have a view model, that has multiple child view models. I am fairly new to watchOS, SwiftUI and Combine - taking this opportunity to learn.
I have a watchUI where it has
Play Button (View) - SetTimerPlayPauseButton
Text to show Time (View) - TimerText
View Model - that has 1 WatchDayProgramViewModel - N: ExerciseTestClass - N: SetInformationTestClass. For each ExerciseSets, there is a watchTimer & watchTimerSubscription and I have managed to run the timer to update remaining rest time.
ContentView - that has been injected the ViewModel as EnvironmentObject
If I tap SetTimerPlayPauseButton to start the timer, timer is running, working and changing the remainingRestTime(property within the child view model SetInformationTestClass) correctly, but the updates/changes are not being "published" to the TimerText View.
I have done most, if not all, the recommendation in other SO answers, I even made all my WatchDayProgramViewModel and ExerciseTestClass,SetInformationTestClass properties #Published, but they are still not updating the View, when the view model properties are updated as shown in the Xcode debugger below.
Please review my code and give me some advice on how to improve it.
ContentView
struct ContentView: View {
#State var selectedTab = 0
#StateObject var watchDayProgramVM = WatchDayProgramViewModel()
var body: some View {
TabView(selection: $selectedTab) {
SetRestDetailView().id(2)
}
.environmentObject(watchDayProgramVM)
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(.page(backgroundDisplayMode: .automatic))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView(watchDayProgramVM: WatchDayProgramViewModel())
}
}
}
SetRestDetailView
import Foundation
import SwiftUI
import Combine
struct SetRestDetailView: View {
#EnvironmentObject var watchDayProgramVM: WatchDayProgramViewModel
var setCurrentHeartRate: Int = 120
#State var showingLog = false
var body: some View {
HStack {
let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
TimerText(elapsedRestTime: elapsedRestTime, totalRestTime: totalRestTime, rect: rect)
.border(Color.yellow)
}
HStack {
SetTimerPlayPauseButton(isSetTimerRunningFlag: false,
playImage: "play.fill",
pauseImage: "pause.fill",
bgColor: Color.clear,
fgColor: Color.white.opacity(0.5),
rect: rect) {
print("playtimer button tapped")
self.watchDayProgramVM.exerciseVMList[0].sets[2].startTimer()
let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
print("printing elapsedRestTime from SetRestDetailView \(elapsedRestTime)")
print("printing elapsedRestTime from SetRestDetailView \(totalRestTime)")
}
.border(Color.yellow)
}
}
}
TimerText
struct TimerText: View {
var elapsedRestTime: Int
var totalRestTime: Int
var rect: CGRect
var body: some View {
VStack {
Text(counterToMinutes())
.font(.system(size: 100, weight: .semibold, design: .rounded))
.kerning(0)
.fontWeight(.semibold)
.minimumScaleFactor(0.25)
.padding(-1)
}
}
func counterToMinutes() -> String {
let currentTime = totalRestTime - elapsedRestTime
let seconds = currentTime % 60
let minutes = Int(currentTime / 60)
if currentTime > 0 {
return String(format: "%02d:%02d", minutes, seconds)
}
else {
return ""
}
}
}
ViewModel
import Combine
final class WatchDayProgramViewModel: ObservableObject {
#Published var exerciseVMList: [ExerciseTestClass] = [
(static/hard-coded values for testing)
]
class ExerciseTestClass: ObservableObject {
init(exercise: String, sets: [SetInformationTestClass]) {
self.exercise = exercise
self.sets = sets
}
var exercise: String
#Published var sets: [SetInformationTestClass]
}
class SetInformationTestClass: ObservableObject {
init(totalRestTime: Int, elapsedRestTime: Int, remainingRestTime: Int, isTimerRunning: Bool) {
self.totalRestTime = totalRestTime
self.elapsedRestTime = elapsedRestTime
self.remainingRestTime = remainingRestTime
self.isTimerRunning = isTimerRunning
}
#Published var totalRestTime: Int
#Published var elapsedRestTime: Int
#Published var remainingRestTime: Int
#Published var isTimerRunning = false
#Published var watchTimer = Timer.publish(every: 1.0, on: .main, in: .default)
#Published var watchTimerSubscription: AnyCancellable? = nil
#Published private var startTime: Date? = nil
func startTimer() {
print("startTimer initiated")
self.watchTimerSubscription?.cancel()
if startTime == nil {
startTime = Date()
}
self.isTimerRunning = true
self.watchTimerSubscription = watchTimer
.autoconnect()
.sink(receiveValue: { [weak self] _ in
guard let self = self, let startTime = self.startTime else { return }
let now = Date()
let elapsedTime = now.timeIntervalSince(startTime)
self.remainingRestTime = self.totalRestTime - Int(elapsedTime)
self.elapsedRestTime = self.totalRestTime - self.remainingRestTime
guard self.remainingRestTime > 0 else {
self.pauseTimer()
return
}
self.objectWillChange.send()
print("printing elapsedRest Time \(self.elapsedRestTime) sec")
print("printing remaining Rest time\(self.remainingRestTime)sec ")
})
}
func pauseTimer() {
//stop timer and retain elapsed rest time
print("pauseTimer initiated")
self.watchTimerSubscription?.cancel()
self.watchTimerSubscription = nil
self.isTimerRunning = false
self.startTime = nil
}
Managed to resolve the issue with help of #lorem ipsum and his feedback. As per his comment, the problem lied with the fact that
it is more than likely not working because you are chaining ObservableObjects #Published will only detect a change when the object is changed as a whole now when variables change. One way to test is to wrap each SetInformationTestClass in an #ObservbleObject by using a subview that takes the object as a parameter.
After which, I managed to find similar SO answers on changes in nested view model (esp child), and made the child view model an ObservedObject. The changes in child view model got populated to the view. Please see the changed code below.
SetRestDetailView
import Foundation
import SwiftUI
import Combine
struct SetRestDetailView: View {
#EnvironmentObject var watchDayProgramVM: WatchDayProgramViewModel
var setCurrentHeartRate: Int = 120
#State var showingLog = false
var body: some View {
HStack {
let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
let setInformatationVM = self.watchDayProgramVM.exerciseVMList[0].sets[2]
TimerText(setInformationVM: setInformatationVM, rect: rect)
.border(Color.yellow)
}
HStack {
SetTimerPlayPauseButton(isSetTimerRunningFlag: false,
playImage: "play.fill",
pauseImage: "pause.fill",
bgColor: Color.clear,
fgColor: Color.white.opacity(0.5),
rect: rect) {
print("playtimer button tapped")
self.watchDayProgramVM.exerciseVMList[0].sets[2].startTimer()
let elapsedRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].elapsedRestTime
let totalRestTime = watchDayProgramVM.exerciseVMList[0].sets[2].totalRestTime
print("printing elapsedRestTime from SetRestDetailView \(elapsedRestTime)")
print("printing elapsedRestTime from SetRestDetailView \(totalRestTime)")
}
.border(Color.yellow)
}
}
}
TimerText
struct TimerText: View {
#ObservedObject var setInformationVM: SetInformationTestClass
// #State var elapsedRestTime: Int
// #State var totalRestTime: Int
var rect: CGRect
var body: some View {
VStack {
Text(counterToMinutes())
.font(.system(size: 100, weight: .semibold, design: .rounded))
.kerning(0)
.fontWeight(.semibold)
.minimumScaleFactor(0.25)
.padding(-1)
}
}
func counterToMinutes() -> String {
let currentTime = setInformationVM.totalRestTime - setInformationVM.elapsedRestTime
let seconds = currentTime % 60
let minutes = Int(currentTime / 60)
if currentTime > 0 {
return String(format: "%02d:%02d", minutes, seconds)
}
else {
return ""
}
}
}

SwiftUI | why does the wheelPicker not work as expected if a Timer is scheduled?

I have problem with making a selection with a wheelPicker if timer is running simultaneously.
A) default as runloop mode of timer:
the wheelPicker works fine and updates $selection But the counter in my ContenView won't keep counting while the wheelPicker is spinning
B) common as runloop mode of timer
the counter keeps counting while I interact with the wheelPicker, but the wheel pickers selection can't be changed if the timer fires before the picker is done spinning.
Here is my class holding the timer:
import Foundation
class MyTimer: ObservableObject {
private var timer : Timer?
#Published var counter : Int = 0
public func launchTimer(){
self.timer = Timer.scheduledTimer( withTimeInterval: 0.2, repeats: true, block:{_ in
self.counter += 1
})
if self.timer != nil {
RunLoop.current.add(timer!, forMode: .common)
}
}
}
Here is my contentView
import SwiftUI
struct ContentView: View {
#StateObject var myTimer : MyTimer = MyTimer()
#State var selecion : Int = 0
var body: some View {
VStack {
Text("counter: \(myTimer.counter)")
.padding()
Text("selection: \(self.selecion)")
.padding()
Picker(selection: $selecion, label: Text("picker") ) {
ForEach(0..<50, id: \.self) { id in
Text(String(id))
}
}
.pickerStyle(WheelPickerStyle())
Button(action: {
myTimer.launchTimer()
}, label: {
Text("launch timer")
})
}
}
}
I am fully aware, the code above has other issues, since the Timer could be scheduled multiple times. But I hope it is sufficient for illustrational purposes.

SwiftUI: EnvironmentObject loads only the first time

in my current project I work with an EnvironmentObject but I have a very strange bug, maybe someone can help.
First the code:
Model:
class Daten: ObservableObject{
#Published var intervallInsgesamt: Double = UserDefaults.standard.double(forKey: Keys.intervallInsgesamt){
didSet{
UserDefaults.standard.set(self.intervallInsgesamt, forKey: Keys.intervallInsgesamt)
}
}
#Published var pauseInsgesamt: Double = UserDefaults.standard.double(forKey: Keys.pauseInsgesamt ){
didSet{
UserDefaults.standard.set(self.pauseInsgesamt, forKey: Keys.pauseInsgesamt)
}
}
...
}
First View:
#EnvironmentObject var daten: Daten
var body: some View {
NavigationView{
GeometryReader { geometry in
VStack{
ScrollView(showsIndicators: false){
...
}
//IMPORTANT START
NavigationLink(destination: TrainingView().environmentObject(daten)){ //!!!
Text("Start")
.styleButton()
}
//IMPORTANT END
}
}
}
}
Second View (TraingsView):
struct TrainingView: View {
#EnvironmentObject var daten: Daten
#State private var isRunning = false
#State private var to: CGFloat = 0
#State private var insgesamt = 0.0
#State private var minuten = 0
#State private var sekunden = 0
#State private var durchgang = 1
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State private var zähl = 0
#State private var color = Color.red
#State private var übung = 0
#State private var momentaneÜbung = ""
#State private var nächsteÜbung = ""
#State private var teilerTo = 0
var body: some View {
ZStack{
VStack{
...
}
HStack(spacing: 50){
Button(action:{
isRunning = true
Sounds.playSounds(soundfile: "sound.wav")
}) {
Text("Start")
}
Button(action:{
isRunning = false
Sounds.playSounds(soundfile: "sound.wav")
}) {
Text("Stop")
}
}
}
}
.onReceive(timer) { _ in
if isRunning{
if insgesamt > 0{
print("1. Durchgang: \(durchgang), Übung: \(übung), momenaten: \(momentaneÜbung), nächste: \(nächsteÜbung)")
insgesamt -= 1.0
minuten = Int(insgesamt/60)
sekunden = Int(insgesamt.truncatingRemainder(dividingBy: 60))
zähl += 1
withAnimation(.default) {
to = CGFloat(Double(zähl) / Double(teilerTo))
}
}else{
... stuff that should run when insgesamt <= 0 but is not important for my problem
}
}
}
.onAppear {
teilerTo = Int(daten.intervallInsgesamt)
print("Teiler: \(teilerTo)")
insgesamt = daten.intervallInsgesamt
minuten = Int(insgesamt/60)
sekunden = Int(insgesamt.truncatingRemainder(dividingBy: 60))
if !daten.übungen.isEmpty{
momentaneÜbung = daten.übungen[übung].name
if übung+1 < daten.übungen.count{
nächsteÜbung = "Pause"
}else{
nächsteÜbung = "Nächster Durchgang"
}
}
}
}
}
NOW THE PROBLEM:
When I start the app, set the different times, go to the TrainingsView, everything works fine. Then I go back to the first View(change the times or not, it doesn't matter) and go back to the TraingsView. Now nothing works! It doesn't find the EnvironmentObject anymore. I don't know why because the first time it did. Does somebody know why?
(An other problem I is, that print() doesn't work. I can't see the things that should print out. I don't know if that is important...)
Thanks in advance!
In your root view, you should declare your object as an observable object:
#ObservableObject var daten = Daten()
then you should use a .environmentObject() modifier on top of your first View:
// pass the daten object that was declared as observable object here
.environmentObject(daten)
from now on, on other descendant views, you can get your model by declaring it as an environment object:
#EnvironmentObject var daten: Daten
// gives you the same old daten model

SwiftUI - How to Make A Start/Stop Timer

My goal is to create a view in SwiftUI that starts with 0. When you press the view, a timer should start counting upwards, and tapping again stops the timer. Finally, when you tap again to start the timer, the timer should begin at 0.
Here is my current code:
import SwiftUI
struct TimerView: View {
#State var isTimerRunning = false
#State private var endTime = Date()
#State private var startTime = Date()
let timer = Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()
var tap: some Gesture {
TapGesture(count: 1)
.onEnded({
isTimerRunning.toggle()
})
}
var body: some View {
Text("\(endTime.timeIntervalSince1970 - startTime.timeIntervalSince1970)")
.font(.largeTitle)
.gesture(tap)
.onReceive(timer) { input in
startTime = isTimerRunning ? startTime : Date()
endTime = isTimerRunning ? input : endTime
}
}
}
This code causes the timer to start instantly and never stop, even when I tap on it. The timer also goes backward (into negative numbers) rather than forward.
Can someone please help me understand what I am doing wrong? Also, I would like to know if this is a good overall strategy for a timer (using Timer.publish).
Thank you!
Here is a fixed version. Take a look at the changes I made.
.onReceive now updates a timerString if the timer is running. The timeString is the interval between now (ie. Date()) and the startTime.
Tapping on the timer sets the startTime if it isn't running.
struct TimerView: View {
#State var isTimerRunning = false
#State private var startTime = Date()
#State private var timerString = "0.00"
let timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
var body: some View {
Text(self.timerString)
.font(Font.system(.largeTitle, design: .monospaced))
.onReceive(timer) { _ in
if self.isTimerRunning {
timerString = String(format: "%.2f", (Date().timeIntervalSince( self.startTime)))
}
}
.onTapGesture {
if !isTimerRunning {
timerString = "0.00"
startTime = Date()
}
isTimerRunning.toggle()
}
}
}
The above version, while simple, bugs me that the Timer is publishing all the time. We only need the Timer publishing when the timer is running.
Here is a version that starts and stops the Timer:
struct TimerView: View {
#State var isTimerRunning = false
#State private var startTime = Date()
#State private var timerString = "0.00"
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
Text(self.timerString)
.font(Font.system(.largeTitle, design: .monospaced))
.onReceive(timer) { _ in
if self.isTimerRunning {
timerString = String(format: "%.2f", (Date().timeIntervalSince( self.startTime)))
}
}
.onTapGesture {
if isTimerRunning {
// stop UI updates
self.stopTimer()
} else {
timerString = "0.00"
startTime = Date()
// start UI updates
self.startTimer()
}
isTimerRunning.toggle()
}
.onAppear() {
// no need for UI updates at startup
self.stopTimer()
}
}
func stopTimer() {
self.timer.upstream.connect().cancel()
}
func startTimer() {
self.timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
}
}
Stop-watch Timer
The following approach allows you create a start/stop/reset SwiftUI Timer using #Published and #ObservedObject property wrappers, along with the ObservableObject protocol.
Here's the ContentView structure:
import SwiftUI
struct ContentView: View {
#ObservedObject var stopWatch = Stop_Watch()
var body: some View {
let minutes = String(format: "%02d", stopWatch.counter / 60)
let seconds = String(format: "%02d", stopWatch.counter % 60)
let union = minutes + " : " + seconds
ZStack {
Color.black.ignoresSafeArea()
VStack {
Spacer()
HStack {
Button("Start") { self.stopWatch.start() }
.foregroundColor(.purple)
Button("Stop") { self.stopWatch.stop() }
.foregroundColor(.orange)
Button("Reset") { self.stopWatch.reset() }
.foregroundColor(.yellow)
}
Spacer()
Text("\(union)")
.foregroundColor(.teal)
.font(.custom("", size: 90))
Spacer()
}
}
}
}
...and Stop_Watch class:
class Stop_Watch: ObservableObject {
#Published var counter: Int = 0
var timer = Timer()
func start() {
self.timer = Timer.scheduledTimer(withTimeInterval: 1.0,
repeats: true) { _ in
self.counter += 1
}
}
func stop() {
self.timer.invalidate()
}
func reset() {
self.counter = 0
self.timer.invalidate()
}
}
Updated for Swift 5.7 and iOS 16 to display a timer that counts up seconds and minutes like a simple stopwatch. Using DateComponentsFormatter to format the minutes and seconds.
struct StopWatchView: View {
#State var isRunning = false
#State private var startTime = Date()
#State private var display = "00:00"
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
Text(display)
.font(.system(size: 20, weight: isRunning ? .bold : .light, design: .monospaced))
.foregroundColor(.accentColor)
.onReceive(timer) { _ in
if isRunning {
let duration = Date().timeIntervalSince(startTime)
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
display = formatter.string(from: duration) ?? ""
}
}
.onTapGesture {
if isRunning {
stop()
} else {
display = "00:00"
startTime = Date()
start()
}
isRunning.toggle()
}
.onAppear {
stop()
}
}
func stop() {
timer.upstream.connect().cancel()
}
func start() {
timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}
}
struct StopWatchView_Previews: PreviewProvider {
static var previews: some View {
StopWatchView()
}
}

SwiftUI instanced #State variable

I am quite new to SwiftUI. I have a following "Counter" view that counts up every second. I want to "reset" the counter when the colour is changed:
struct MyCounter : View {
let color: Color
#State private var count = 0
init(color:Color) {
self.color = color
_count = State(initialValue: 0)
}
var body: some View {
Text("\(count)").foregroundColor(color)
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}
}
}
Here is my main view that uses counter:
struct ContentView: View {
#State var black = true
var body: some View {
VStack {
MyCounter(color: black ? Color.black : Color.yellow)
Button(action:{self.black.toggle()}) { Text("Toggle") }
}
}
}
When i click "Toggle" button, i see MyCounter constructor being called, but #State counter persists and never resets. So my question is how do I reset this #State value? Please note that I do not wish to use counter as #Binding and manage that in the parent view, but rather MyCounter be a self-contained widget. (this is a simplified example. the real widget I am creating is a sprite animator that performs sprite animations, and when I swap the image, i want the animator to start from frame 0). Thanks!
There are two way you can solve this issue. One is to use a binding, like E.Coms explained, which is the easiest way to solve your problem.
Alternatively, you could try using an ObservableObject as a view model for your timer. This is the more flexible solution. The timer can be passed around and it could also be injected as an environment object if you so desire.
class TimerModel: ObservableObject {
// The #Published property wrapper ensures that objectWillChange signals are automatically emitted.
#Published var count: Int = 0
init() {}
func start() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}
func reset() {
count = 0
}
}
Your timer view then becomes
struct MyCounter : View {
let color: Color
#ObservedObject var timer: TimerModel
init(color: Color, timer: TimerModel) {
self.color = color
self.timer = timer
}
var body: some View {
Text("\(timer.count)").foregroundColor(color)
.onAppear(){
self.timer.start()
}
}
}
Your content view becomes
struct ContentView: View {
#State var black = true
#ObservedObject var timer = TimerModel()
var body: some View {
VStack {
MyCounter(color: black ? Color.black : Color.yellow, timer: self.timer)
Button(action: {
self.black.toggle()
self.timer.reset()
}) {
Text("Toggle")
}
}
}
}
The advantage of using an observable object is that you can then keep track of your timer better. You could add a stop() method to your model, which invalidates the timer and you can call it in a onDisappear block of your view.
One thing that you have to be careful about this approach is that when you're using the timer in a standalone fashion, where you create it in a view builder closure with MyCounter(color: ..., timer: TimerModel()), every time the view is rerendered, the timer model is replaced, so you have to make sure to keep the model around somehow.
You need a binding var:
struct MyCounter : View {
let color: Color
#Binding var count: Int
var body: some View {
Text("\(count)").foregroundColor(color)
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}
}
}
struct ContentView: View {
#State var black = true
#State var count : Int = 0
var body: some View {
VStack {
MyCounter(color: black ? Color.black : Color.yellow , count: $count)
Button(action:{self.black.toggle()
self.count = 0
}) { Text("Toggle") }
}
}
}
Also you can just add one State Value innerColor to help you if you don't like binding.
struct MyCounter : View {
let color: Color
#State private var count: Int = 0
#State private var innerColor: Color?
init(color: Color) {
self.color = color
}
var body: some View {
return Text("\(self.count)")
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.count = self.count + 1 }
}.foregroundColor(color).onReceive(Just(color), perform: { color in
if self.innerColor != self.color {
self.count = 0
self.innerColor = color}
})
}
}