코드 전문
import ComposableArchitecture
import SwiftUI
private let readMe = """
This application demonstrates how to work with timers in the Composable Architecture.
It makes use of the `.timer` method on clocks, which is a helper provided by the Swift Clocks \
library included with this library. The helper provides an `AsyncSequence`-friendly API for \
dealing with times in asynchronous code.
"""
@Reducer
struct Timers {
@ObservableState
struct State: Equatable {
var isTimerActive = false
var secondsElapsed = 0
}
enum Action {
case onDisappear
case timerTicked
case toggleTimerButtonTapped
}
@Dependency(\.continuousClock) var clock
private enum CancelID { case timer }
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .onDisappear:
return .cancel(id: CancelID.timer)
case .timerTicked:
state.secondsElapsed += 1
return .none
case .toggleTimerButtonTapped:
state.isTimerActive.toggle()
return .run { [isTimerActive = state.isTimerActive] send in
guard isTimerActive else { return }
for await _ in self.clock.timer(interval: .seconds(1)) {
await send(.timerTicked, animation: .interpolatingSpring(stiffness: 3000, damping: 40))
}
}
.cancellable(id: CancelID.timer, cancelInFlight: true)
}
}
}
}
struct TimersView: View {
var store: StoreOf<Timers>
var body: some View {
Form {
AboutView(readMe: readMe)
ZStack {
Circle()
.fill(
AngularGradient(
gradient: Gradient(
colors: [
.blue.opacity(0.3),
.blue,
.blue,
.green,
.green,
.yellow,
.yellow,
.red,
.red,
.purple,
.purple,
.purple.opacity(0.3),
]
),
center: .center
)
)
.rotationEffect(.degrees(-90))
GeometryReader { proxy in
Path { path in
path.move(to: CGPoint(x: proxy.size.width / 2, y: proxy.size.height / 2))
path.addLine(to: CGPoint(x: proxy.size.width / 2, y: 0))
}
.stroke(.primary, lineWidth: 3)
.rotationEffect(.degrees(Double(store.secondsElapsed) * 360 / 60))
}
}
.aspectRatio(1, contentMode: .fit)
.frame(maxWidth: 280)
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
Button {
store.send(.toggleTimerButtonTapped)
} label: {
Text(store.isTimerActive ? "Stop" : "Start")
.padding(8)
}
.frame(maxWidth: .infinity)
.tint(store.isTimerActive ? Color.red : .accentColor)
.buttonStyle(.borderedProminent)
}
.navigationTitle("Timers")
.onDisappear {
store.send(.onDisappear)
}
}
}
#Preview {
NavigationStack {
TimersView(
store: Store(initialState: Timers.State()) {
Timers()
}
)
}
}
동작화면
리드미 해석
This application demonstrates how to work with timers in the Composable Architecture. It utilizes the .timer method on clocks, which is a helper provided by the Swift Clocks library included with this library. The helper provides an AsyncSequence-friendly API for dealing with times in asynchronous code.
이 애플리케이션은 Composable Architecture에서 타이머를 다루는 방법을 보여줍니다. Swift Clocks 라이브러리에 포함된 .timer 메서드를 활용합니다. 이 메서드는 비동기 코드에서 시간을 다루는데 도움을 주는 헬퍼입니다. 이 헬퍼는 AsyncSequence와 호환되는 API를 제공합니다.
타이머 동작 과정
타이머 버튼을 누를 경우, isTimerActive변수를 통해서 타이머가 움직일지 움직이지 않을지를 결정합니다. 그리고 cancellable에 등록을 합니다. cancellable의 cancelInFlight을 통해서 기존에 작업하던 동작을 취소해 줍니다.
우리는 task를 cancelid 에 할당하고 타이머를 동작시킵니다. 그리고 버튼이 한번더 눌렸을 경우에 이전에 사용하고 있는 cancelid를 통해서 작업을 재 할당 시켜야 합니다. 이 때 과거에 추가된 Task를 취소시켜야 합니다. 이를 cancellable의 cancelFlight를 통해 제거합니다.
cancelFlight: 새로운 것을 시작하기전에, 같은 identifier의 내부 effect 를 취소시킬지 말지를 결정합니다.
case .toggleTimerButtonTapped:
state.isTimerActive.toggle()
return .run { [isTimerActive = state.isTimerActive] send in
guard isTimerActive else { return }
for await _ in self.clock.timer(interval: .seconds(1)) {
await send(.timerTicked, animation: .interpolatingSpring(stiffness: 3000, damping: 40))
}
}
.cancellable(id: CancelID.timer, cancelInFlight: true)
게시물은 TCA 라이브러리의 CaseStudies를 직접 작성해보고 어떻게 작동하는지에 대해서 공부하기 위해서 작성했습니다.
'Swift > TCA' 카테고리의 다른 글
[TCA] Performance 읽어보기 (1) | 2024.08.29 |
---|---|
[TCA] Effect.swift 공식문서 음미하기 (0) | 2024.08.24 |
[TCA] day 12 새로고침 (Effects-Refreshable) (0) | 2024.05.07 |
[TCA] day 10 long living Effect (publisher Effect and async/await effect) (0) | 2024.05.05 |
[TCA] day 10 Effect 취소 (TCA @Shared, Sheet) (0) | 2024.05.03 |