Files
swift-composable-architectu…/Examples/CaseStudies/SwiftUICaseStudies/03-Effects-LongLiving.swift
Stephen Celis f9f3e3a4cb Use AnyHashableSendable from Concurrency Extras (#3428)
* Use `AnyHashableSendable` from Concurrency Extras

Rather than use an ad hoc implementation with an `AnyHashable` under the
hood that may not be concurrency safe, let's adopt the helper we added
to the Concurrency Extras packages.

* fix

* wip

* wip
2024-10-08 13:08:38 -07:00

110 lines
2.8 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import ComposableArchitecture
import SwiftUI
private let readMe = """
This application demonstrates how to handle long-living effects, for example notifications from \
Notification Center, and how to tie an effect's lifetime to the lifetime of the view.
Run this application in the simulator, and take a few screenshots by going to \
*Device Screenshot* in the menu, and observe that the UI counts the number of times that \
happens.
Then, navigate to another screen and take screenshots there, and observe that this screen does \
*not* count those screenshots. The notifications effect is automatically cancelled when leaving \
the screen, and restarted when entering the screen.
"""
@Reducer
struct LongLivingEffects {
@ObservableState
struct State: Equatable {
var screenshotCount = 0
}
enum Action {
case task
case userDidTakeScreenshotNotification
}
@Dependency(\.screenshots) var screenshots
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .task:
// When the view appears, start the effect that emits when screenshots are taken.
return .run { send in
for await _ in await self.screenshots() {
await send(.userDidTakeScreenshotNotification)
}
}
case .userDidTakeScreenshotNotification:
state.screenshotCount += 1
return .none
}
}
}
}
extension DependencyValues {
var screenshots: @Sendable () async -> any AsyncSequence<Void, Never> {
get { self[ScreenshotsKey.self] }
set { self[ScreenshotsKey.self] = newValue }
}
}
private enum ScreenshotsKey: DependencyKey {
static let liveValue: @Sendable () async -> any AsyncSequence<Void, Never> = {
NotificationCenter.default
.notifications(named: UIApplication.userDidTakeScreenshotNotification)
.map { _ in }
}
}
struct LongLivingEffectsView: View {
let store: StoreOf<LongLivingEffects>
var body: some View {
Form {
Section {
AboutView(readMe: readMe)
}
Text("A screenshot of this screen has been taken \(store.screenshotCount) times.")
.font(.headline)
Section {
NavigationLink {
detailView
} label: {
Text("Navigate to another screen")
}
}
}
.navigationTitle("Long-living effects")
.task { await store.send(.task).finish() }
}
var detailView: some View {
Text(
"""
Take a screenshot of this screen a few times, and then go back to the previous screen to see \
that those screenshots were not counted.
"""
)
.padding(.horizontal, 64)
.navigationBarTitleDisplayMode(.inline)
}
}
#Preview {
NavigationStack {
LongLivingEffectsView(
store: Store(initialState: LongLivingEffects.State()) {
LongLivingEffects()
}
)
}
}