HIT해

[iOS/SwiftUI] 스크린샷 감지 TCA 본문

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)
    }
  }