-
When I use Dispatch Queue, Timer work even if I do other actions. However, when you try to run it with Runloop, Timer stops. How can I use Runloop and make other actions possible? 2021-12-08.6.02.07.mov// MARK: - Timer Runloop
import ComposableArchitecture
import SwiftUI
struct TimersStateRunloop: Equatable {
var isTimerActive = false
var secondsElapsed: Float = 0.0
var sliderValue: Float = 0.0
}
enum TimersActionRunloop {
case timerTicked
case toggleTimerButtonTapped
case sliderValueChanged(Float)
case sliderValueChanged2(Float)
}
struct TimersEnvironmentRunloop {
var mainRunloop: AnySchedulerOf<RunLoop>
}
let timersReducerRunloop = Reducer<TimersStateRunloop, TimersActionRunloop, TimersEnvironmentRunloop> {
state, action, environment in
struct TimerId: Hashable {}
switch action {
case .timerTicked:
state.secondsElapsed += 100 / 1000
return .none
case .toggleTimerButtonTapped:
state.isTimerActive.toggle()
return state.isTimerActive
? Effect.timer(
id: TimerId(),
every: 0.1,
on: environment.mainRunloop
)
.map { _ in TimersActionRunloop.timerTicked }
: Effect.cancel(id: TimerId())
case let .sliderValueChanged(value):
state.secondsElapsed += value
return .none
case let .sliderValueChanged2(value):
state.sliderValue = value
return .none
}
}
// MARK: - Timer feature view
struct TimersViewRunloop: View {
@ObservedObject var viewStore: ViewStore<ViewState, TimersActionRunloop>
init(store: Store<TimersStateRunloop, TimersActionRunloop>) {
self.viewStore = ViewStore(store.scope(state: ViewState.init))
}
struct ViewState: Equatable {
var isTimerActive = false
var secondsElapsed: Float = 0.0
var sliderValue: Float = 0.0
init(state: TimersStateRunloop) {
self.isTimerActive = state.isTimerActive
self.secondsElapsed = state.secondsElapsed
self.sliderValue = state.sliderValue
}
}
var body: some View {
VStack {
Slider(
value: viewStore.binding(
get: \.secondsElapsed,
send: TimersActionRunloop.sliderValueChanged),
in: 0...30
)
Text("secondsElapsed: \(viewStore.secondsElapsed)")
Slider(
value: viewStore.binding(get: \.sliderValue, send: TimersActionRunloop.sliderValueChanged2),
in: 0...30
)
Text("sliderValue: \(viewStore.sliderValue)")
Button(action: { self.viewStore.send(.toggleTimerButtonTapped) }) {
HStack {
Text(self.viewStore.isTimerActive ? "Stop" : "Start")
}
.foregroundColor(.white)
.padding()
.background(self.viewStore.isTimerActive ? Color.red : .blue)
.cornerRadius(16)
}
Spacer()
}
.padding()
}
} //MARK: - Timer DispatchQueue
import ComposableArchitecture
import SwiftUI
// MARK: - Timer feature domain
struct TimersStateDispatchQueue: Equatable {
var isTimerActive = false
var secondsElapsed: Float = 0.0
var sliderValue: Float = 0.0
}
enum TimersActionDispatchQueue {
case timerTicked
case toggleTimerButtonTapped
case sliderValueChanged(Float)
case sliderValueChanged2(Float)
}
struct TimersEnvironmentDispatchQueue {
var mainQueue: AnySchedulerOf<DispatchQueue>
}
let timersReducerDispatchQueue = Reducer<TimersStateDispatchQueue, TimersActionDispatchQueue, TimersEnvironmentDispatchQueue> {
state, action, environment in
struct TimerId: Hashable {}
switch action {
case .timerTicked:
state.secondsElapsed += 100 / 1000
return .none
case .toggleTimerButtonTapped:
state.isTimerActive.toggle()
return state.isTimerActive
? Effect.timer(
id: TimerId(),
every: 0.1,
on: environment.mainQueue
)
.map { _ in TimersActionDispatchQueue.timerTicked }
: Effect.cancel(id: TimerId())
case let .sliderValueChanged(value):
state.secondsElapsed += value
return .none
case let .sliderValueChanged2(value):
state.sliderValue = value
return .none
}
}
// MARK: - Timer feature view
struct TimersViewDispatchQueue: View {
@ObservedObject var viewStore: ViewStore<ViewState, TimersActionDispatchQueue>
init(store: Store<TimersStateDispatchQueue, TimersActionDispatchQueue>) {
self.viewStore = ViewStore(store.scope(state: ViewState.init))
}
struct ViewState: Equatable {
var isTimerActive = false
var secondsElapsed: Float = 0.0
var sliderValue: Float = 0.0
init(state: TimersStateDispatchQueue) {
self.isTimerActive = state.isTimerActive
self.secondsElapsed = state.secondsElapsed
self.sliderValue = state.sliderValue
}
}
var body: some View {
VStack {
Slider(
value: viewStore.binding(
get: \.secondsElapsed,
send: TimersActionDispatchQueue.sliderValueChanged),
in: 0...30
)
Text("secondsElapsed: \(viewStore.secondsElapsed)")
Slider(
value: viewStore.binding(get: \.sliderValue, send: TimersActionDispatchQueue.sliderValueChanged2),
in: 0...30
)
Text("sliderValue: \(viewStore.sliderValue)")
Button(action: { self.viewStore.send(.toggleTimerButtonTapped) }) {
HStack {
Text(self.viewStore.isTimerActive ? "Stop" : "Start")
}
.foregroundColor(.white)
.padding()
.background(self.viewStore.isTimerActive ? Color.red : .blue)
.cornerRadius(16)
}
Spacer()
}
.padding()
}
} import SwiftUI
import ComposableArchitecture
struct ContentView: View {
var body: some View {
VStack {
TimersView(
store: Store(
initialState: TimersState(),
reducer: timersReducer,
environment: TimersEnvironment(
mainQueue: .main
)
)
)
}
Divider()
VStack {
Text("Runloop")
TimersViewRunloop(
store: .init(
initialState: TimersStateRunloop(),
reducer: timersReducerRunloop,
environment: TimersEnvironmentRunloop(
mainRunloop: .main
)
)
)
}
Divider()
VStack {
Text("DispatchQueue")
TimersViewDispatchQueue(
store: .init(
initialState: TimersStateDispatchQueue(),
reducer: timersReducerDispatchQueue,
environment: TimersEnvironmentDispatchQueue(
mainQueue: .main
)
)
)
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hi @wogus3602, unfortunately I don't think this is possible. The problem is that The only way to schedule a run loop timer in common mode is to use Unless others have ideas, I think you are stuck with using a |
Beta Was this translation helpful? Give feedback.
Hi @wogus3602, unfortunately I don't think this is possible.
The problem is that
RunLoop
's conformance to theScheduler
protocol usesRunLoop.Mode.default
, which is paused whenever there is user interaction. There doesn't seem to be any way to force a run loop scheduler to run in.common
mode, which continues even when there is user interaction.The only way to schedule a run loop timer in common mode is to use
Timer.publisher
, but then it will be a completely uncontrolled timer, and so will not be easy to test.Unless others have ideas, I think you are stuck with using a
DispatchQueue
as your scheduler if you want the timer to continue during user interaction.