Replies: 2 comments
-
@wshamp I chipped away at the TCA elements of your example and saw the same behavior in vanilla SwiftUI. You can see below, but you might want to file feedback with Apple, since TCA doesn't seem to be part of the problem. import SwiftUI
struct TimerState: Equatable {
var elapsed: TimeInterval = 0.0
var timerIsActive: Bool = false
var testOption: TestOption = .one
var formatedToHoursMinSec: String {
let totalHundredths = Int(elapsed * 100)
let hours = totalHundredths / 360000
let minutes = (totalHundredths % 360000) / 6000
let seconds = (totalHundredths % 6000) / 100
let hundredths = totalHundredths % 100
return String(format: "%d:%02d:%02d.%02d", hours, minutes, seconds, hundredths)
}
}
struct TimerView: View {
@State var state = TimerState()
var body: some View {
VStack {
Text(state.formatedToHoursMinSec)
HStack {
Button {
state.timerIsActive = false
state.elapsed = 0.0
} label: {
Text("Reset")
}
Spacer().frame(width: 50)
Button {
if state.timerIsActive {
state.timerIsActive = false
} else {
state.timerIsActive = true
}
} label: {
Text(state.timerIsActive ? "Stop" : "Start")
}
}
.padding(.top, 30)
HStack {
Spacer()
Picker("TestOption", selection: $state.testOption) {
ForEach(TestOption.allCases) { option in
Text(option.rawValue)
}
}
.pickerStyle(.segmented)
.frame(width: 150)
Spacer()
}
.padding(.top, 30)
}
.task {
for await _ in ContinuousClock().timer(interval: .seconds(0.01)) {
if state.timerIsActive {
state.elapsed += 0.01
}
}
}
}
}
#Preview {
TimerView()
}
enum TestOption: String, Equatable, CaseIterable, Identifiable {
public var id: Self { self }
case one = "One"
case two = "Two"
} |
Beta Was this translation helpful? Give feedback.
0 replies
-
So I was able to solve the issue by observing the picker separate from the rest of the view. It is interesting this is an issue in vanilla swiftUI as well as timers like this are very common in iOS. Any way for those who may be facing a similar issue here is how I fixed it. import SwiftUI
import ComposableArchitecture
struct TimerFeature: Reducer {
@Dependency(\.continuousClock) var clock
struct State: Equatable {
var elapsed: TimeInterval = 0.0
var timerIsActive: Bool = false
@BindingState var testOption: TestOption = .one
var formatedToHoursMinSec: String {
let totalHundredths = Int(elapsed * 100)
let hours = totalHundredths / 360000
let minutes = (totalHundredths % 360000) / 6000
let seconds = (totalHundredths % 6000) / 100
let hundredths = totalHundredths % 100
return String(format: "%d:%02d:%02d.%02d", hours, minutes, seconds, hundredths)
}
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
case startStopButtonTapped
case resetButtonTapped
case timerTicked
case onTask
}
private enum CancelID {
case timer
}
var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .onTask:
return .run { send in
for await _ in self.clock.timer(interval: .seconds(0.01)) {
await send(.timerTicked)
}
}.cancellable(id: CancelID.timer)
case .binding(\.$testOption):
switch state.testOption {
case .one:
print("one")
case .two:
print("two")
}
return .none
case .binding:
return .none
case .timerTicked:
if state.timerIsActive {
state.elapsed += 0.01
}
return .none
case .startStopButtonTapped:
if state.timerIsActive {
state.timerIsActive = false
} else {
state.timerIsActive = true
}
return .none
case .resetButtonTapped:
state.timerIsActive = false
state.elapsed = 0.0
return .none
}
}
}
}
struct TimerView: View {
let store: StoreOf<TimerFeature>
public init(store: StoreOf<TimerFeature>) {
self.store = store
}
struct PickerViewState: Equatable {
@BindingViewState var testOption: TestOption
init(bindingViewStore: BindingViewStore<TimerFeature.State>) {
self._testOption = bindingViewStore.$testOption
}
}
var body: some View {
VStack {
WithViewStore(self.store, observe: \.formatedToHoursMinSec) { viewStore in
VStack {
Text(viewStore.state)
HStack {
Button {
viewStore.send(.resetButtonTapped)
} label: {
Text("Reset")
}
Spacer().frame(width: 50)
Button {
viewStore.send(.startStopButtonTapped)
} label: {
Text("Toggle Timer")
}
}
.padding(.top, 30)
}
.task { await viewStore.send(.onTask).finish() }
}
WithViewStore(self.store, observe: PickerViewState.init) { viewStore in
HStack {
Spacer()
Picker("TestOption", selection: viewStore.$testOption) {
ForEach(TestOption.allCases) { option in
Text(option.rawValue)
}
}
.pickerStyle(.segmented)
.frame(width: 150)
Spacer()
}
.padding(.top, 30)
}
}
}
}
#Preview {
TimerView(store: Store(initialState: TimerFeature.State()) {
TimerFeature()
})
}
enum TestOption: String, Equatable, CaseIterable, Identifiable {
public var id: Self { self }
case one = "One"
case two = "Two"
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
So I am seeing a strange effect where if I am running a timer and I have a segmented picker with a binding, sometimes action fires but does not change the value. This only happens when the timer is active. when I stop the timer the picker works as expected This is reproducible please see the following example. Am I doing something wrong?
When the timer is running after I tap start when I switch between option 1 and 2 sometimes it will not switch but the print statement fires.
This does not happen when the timer is not running.
Beta Was this translation helpful? Give feedback.
All reactions