79. Пример работы с debounce в Combine
Кто еще застал RxSwift вполне мог работать с debounce для реализации отложенного поиска, чтобы начинать поиск с небольшой задержкой после ввода текста. В этой статье посмотрим как можно это реализовать с помощью Combine.
Демонстрация
На гифке выше пример дебаунса в 1 секунду - после ввода нового символа мы ждем 1 секунду, чтобы выполнить действие. Тут и далее в качестве примера действия будем отображать введенный текст под текстовым полем.
На гифке выше мы вводим текст почти без пауз, т.е. между каждым введенным символом меньше 1 секунды, и только когда мы останавливаемся дольше чем на 1 секунду, выполняется действие после дебаунса.
Реализация
Есть разные варианты реализации такой фичи. Для простоты покажу вариант с меньшим количеством кода на Combine, а в самой вьюхе пока что будем использовать обычное текстовое поле.
Последовательность наших действий:
- Подписаться на изменения текста в первом свойстве
- Добавить дебаунс для подписки
- Выводить значение после дебаунса в другое свойство
- Подключить к вьюхе класс в подпиской и двумя вышеупомянутыми свойствами
Вьюмодель
final class DebounceViewModel : ObservableObject {
@Published var searchQuery = ""
@Published private(set) var debouncedText = ""
init(delay: DispatchQueue.SchedulerTimeType.Stride) {
$searchQuery
.debounce(for: delay, scheduler: DispatchQueue.main)
.assign(to: &$debouncedText)
}
}
Вьюха
struct DebounceExampleView: View {
@StateObject private var viewModel: DebounceViewModel
init(debounceDelay: DispatchQueue.SchedulerTimeType.Stride) {
self._viewModel = .init(wrappedValue: .init(delay: debounceDelay))
}
var body: some View {
VStack(spacing: 20) {
TextField("Что ищем?", text: $viewModel.searchQuery)
.textFieldStyle(.roundedBorder)
if !viewModel.debouncedText.isEmpty {
Text("Пользователь ввел запрос для поиска:")
Text(viewModel.debouncedText)
}
}
.padding()
Spacer()
}
}
Превью
#Preview("1 секунда") {
DebounceExampleView(debounceDelay: .seconds(1))
}
#Preview("0.5 секунды") {
DebounceExampleView(debounceDelay: .seconds(0.5))
}
Что дальше
Теперь мы отслеживаем нужную нам паузу при вводе текста и можем реализовать функционал поиска - это можно сделать разными способами, например:
- Добавить во вьюху модификатор onChange/onReceive, в котором стартовать поиск
- Добавить во вьюмодель подписку на debouncedText, и при ее срабатывании стартовать поиск
- Можно сразу без лишнего действия с обновлением debouncedText вызывать поиск после дебаунса во вьюмодели
В любом случае нужно продумать логику поиска и обработать разные важные кейсы, например:
- поиск уже стартовали, а пользователь продолжает вводить текст (и предыдущий поиск возвращает результаты/ошибку)
- пользователь вводит пробелы или несколько слов - нужно дропнуть лишние пробелы и корректно закодировать оставшиеся для отправки на сервер (если есть интеграция с сервером)
Заключение
В данном случае я знал, что для решения рабочей задачи мне нужен debounce, и было несложно загуглить решение. Если знать, что нужно гуглить, то разобраться в Combine может быть проще, чем в RxSwift. Как минимум из-за наличия меньшего объема фичей, ведь RxSwift годами развивался за счет сообщества, а Combine сильно моложе.
Код для этой статьи можно посмотреть тут, другие статьи по разработке - тут, а про инвестиции - тут.