【SwiftUI】TextField で debounce | Debouncing TextField

みんなは、どんな実装にしていますか。

入力文字を監視しつつの検索結果のリアルタイム反映的なやつを作るとき。

詰まる感じをどう解消していますか。

Debounce
only emit an item from an Observable if a particular timespan has passed without it emitting another item

👉 ReactiveX - Debounce operator hatena-bookmark

👉 debounce(for:scheduler:options:) | Apple Developer Documentation hatena-bookmark


import Combine

public final class DebounceObject: ObservableObject {
  @Published var text: String = ""
  @Published var debouncedText: String = ""
  private var bag = Set<AnyCancellable>()

  public init(dueTime: TimeInterval = 0.5) {
    $text
      .removeDuplicates()
      .debounce(for: .seconds(dueTime), scheduler: DispatchQueue.main)
      .sink(receiveValue: { [weak self] value in
        self?.debouncedText = value
      })
      .store(in: &bag)
  }
}


struct SearchView: View {
  @StateObject var debounceObject = DebounceObject()

  var body: some View {
    VStack {
      TextField(text: $debounceObject.text)
        .onChange(of: debounceObject.debouncedText) { text in
          // perform search
        }
    }
  }
}

👉 How to debounce TextField search in SwiftUI | Swift Discovery hatena-bookmark

combine てのもう時代遅れなのですか ?

今現在、本流の本筋の考え方のわかりやすいシンプルな記述を探します。

 

🤔 Tunous/DebouncedOnChange

非同期処理とはいえ、cancel や exception の伝播は無視してもいいのではないか。


extension Task {
  @discardableResult
  public static func delayed(
    seconds: TimeInterval,
    operation: @escaping @Sendable () async -> Void
  ) -> Self where Success == Void, Failure == Never {
    Self {
      do {
        try await Task<Never, Never>.sleep(seconds: seconds)
        await operation()
      } catch {}
    }
  }

  static func sleep(seconds: TimeInterval) async throws where Success == Never, Failure == Never {
    try await Task<Success, Failure>.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
  }
}

👉 Tunous/DebouncedOnChange: SwiftUI onChange View extension with debounce time hatena-bookmark

( 引き続き調査中... )