Swift/Swift 개발 노트
[iOS/SwiftUI] 스크린샷 감지 TCA
힛해
2024. 8. 30. 17:33
728x90
전체 코드
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 -> AsyncStream<Void> {
get { self[ScreenshotsKey.self] }
set { self[ScreenshotsKey.self] = newValue }
}
}
private enum ScreenshotsKey: DependencyKey {
static let liveValue: @Sendable () async -> AsyncStream<Void> = {
await AsyncStream(
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()
}
)
}
}
리듀서를 살펴보자
@Dependency(\.screenshots) var screenshots
// screenshot이 감지되었을때 실행시킬 비동기함수
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)
}
}
그리고 뷰의 코드중
.task { await store.send(.task).finish() }
이 코드를 통해 계속해서 실행시키는 것이다.
스크린샷을 찍으면 해당 찍은 카운트 숫자에 대한 정보를 통신하는데 아래의 코드 때문이라고 한다
extension DependencyValues {
var screenshots: @Sendable () async -> AsyncStream<Void> {
get { self[ScreenshotsKey.self] }
set { self[ScreenshotsKey.self] = newValue }
}
}
private enum ScreenshotsKey: DependencyKey {
static let liveValue: @Sendable () async -> AsyncStream<Void> = {
await AsyncStream(
NotificationCenter.default
.notifications(named: UIApplication.userDidTakeScreenshotNotification)
.map { _ in }
)
}
}
코드 설명
1. screenshots 의존성 정의
이 코드는 screenshots라는 의존성을 정의합니다. 이는 AsyncStream<Void>를 반환하는 비동기 함수로, 스크린샷이 찍힐 때마다 이벤트를 발생시키는 스트림을 생성한다.
extension DependencyValues {
var screenshots: @Sendable () async -> AsyncStream<Void> {
get { self[ScreenshotsKey.self] }
set { self[ScreenshotsKey.self] = newValue }
}
}
정의해야하는 함수가 하나일때는 이렇게 정의하나보다.
2. 스크린샷 이벤트 감지
여기에서 ScreenshotsKey는 스크린샷 이벤트를 감지하는 기능을 제공합니다. NotificationCenter를 통해 UIApplication.userDidTakeScreenshotNotification 알림을 받으면, 이 알림을 AsyncStream으로 변환하여 이벤트 스트림을 생성합니다. map { _ in }는 알림에서 아무 정보를 가져오지 않고 단순히 이벤트를 발생시키는 역할을 합니다.
private enum ScreenshotsKey: DependencyKey {
static let liveValue: @Sendable () async -> AsyncStream<Void> = {
await AsyncStream(
NotificationCenter.default
.notifications(named: UIApplication.userDidTakeScreenshotNotification)
.map { _ in }
)
}
}
3. LongLivingEffects와 연결
이 코드는 screenshots 스트림을 구독하고, 스크린샷 이벤트가 발생할 때마다 .userDidTakeScreenshotNotification 액션을 트리거합니다. 이 액션은 Reducer에서 처리되며, 스크린샷 카운트 증가와 관련된 로직을 실행할 수 있습니다.
case .task:
return .run { send in
for await _ in await self.screenshots() {
await send(.userDidTakeScreenshotNotification)
}
}