У меня есть часть UISearchBar в UISearchDisplayController, которая используется для отображения результатов поиска как из локального CoreData, так и из удаленного API. Я хочу добиться "задержки" поиска по удаленному API. В настоящее время на каждый набранный пользователем символ отправляется запрос. Но если пользователь печатает особенно быстро, нет смысла отправлять много запросов: было бы полезно дождаться, пока он перестанет печатать. Есть ли способ добиться этого?
При чтении документации предлагается подождать, пока пользователи явно не нажмут на поиск, но я не считаю это идеальным в моем случае.
Проблемы с производительностью. Если операции поиска могут выполняться очень быстро, можно обновлять результаты поиска по мере того, как пользователь вводит текст, путем реализации метода searchBar: textDidChange: в объекте делегата. Однако, если операция поиска занимает больше времени, вам следует подождать, пока пользователь не коснется кнопки «Поиск», прежде чем начинать поиск в методе searchBarSearchButtonClicked :. Всегда выполняйте операции поиска в фоновом потоке, чтобы избежать блокировки основного потока. Это позволяет вашему приложению реагировать на запросы пользователя во время выполнения поиска и улучшать взаимодействие с пользователем.
Отправка большого количества запросов к API - это не проблема локальной производительности, а только предотвращение слишком высокой скорости запросов на удаленном сервере.
благодаря
источник
Ответы:
Попробуйте это волшебство:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{ // to limit network activity, reload half a second after last key press. [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(reload) object:nil]; [self performSelector:@selector(reload) withObject:nil afterDelay:0.5]; }
Быстрая версия:
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { // to limit network activity, reload half a second after last key press. NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: "reload", object: nil) self.performSelector("reload", withObject: nil, afterDelay: 0.5) }
Обратите внимание, что в этом примере вызывается метод с именем reload, но вы можете заставить его вызывать любой метод, который вам нравится!
источник
Для людей, которым это нужно в Swift 4 и новее :
Сделайте это просто с
DispatchWorkItem
подобным здесь .или используйте старый способ Obj-C:
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { // to limit network activity, reload half a second after last key press. NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: "reload", object: nil) self.performSelector("reload", withObject: nil, afterDelay: 0.5) }
РЕДАКТИРОВАТЬ: Версия SWIFT 3
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { // to limit network activity, reload half a second after last key press. NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.reload), object: nil) self.perform(#selector(self.reload), with: nil, afterDelay: 0.5) } func reload() { print("Doing things") }
источник
DispatchWorkItem
как было предложено выше. Работает элегантнее, чем селекторы.Улучшенный Swift 4:
Предполагая, что вы уже соответствуете требованиям
UISearchBarDelegate
, это улучшенная версия Swift 4 ответа VivienG :func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.reload(_:)), object: searchBar) perform(#selector(self.reload(_:)), with: searchBar, afterDelay: 0.75) } @objc func reload(_ searchBar: UISearchBar) { guard let query = searchBar.text, query.trimmingCharacters(in: .whitespaces) != "" else { print("nothing to search") return } print(query) }
Цель реализации cancelPreviousPerformRequests (withTarget :) - предотвратить непрерывный вызов метода
reload()
для каждого изменения в строке поиска (без его добавления, если вы набрали «abc»,reload()
будет вызываться три раза в зависимости от количества добавленных символов) .Улучшение является: в
reload()
методе имеет параметр отправителя , который является панелью поиска; Таким образом, доступ к его тексту - или к любому из его метода / свойств - будет доступен с объявлением его как глобального свойства в классе.источник
Благодаря этой ссылке я нашел очень быстрый и чистый подход. По сравнению с ответом Nirmit в нем отсутствует «индикатор загрузки», однако он выигрывает по количеству строк кода и не требует дополнительных элементов управления. Я первый добавил
dispatch_cancelable_block.h
файл в мой проект (из этого репо ), а затем определили следующую переменную класса:__block dispatch_cancelable_block_t searchBlock;
.Мой поисковый код теперь выглядит так:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { if (searchBlock != nil) { //We cancel the currently scheduled block cancel_block(searchBlock); } searchBlock = dispatch_after_delay(searchBlockDelay, ^{ //We "enqueue" this block with a certain delay. It will be canceled if the user types faster than the delay, otherwise it will be executed after the specified delay [self loadPlacesAutocompleteForInput:searchText]; }); }
Ноты:
loadPlacesAutocompleteForInput
Является частью LPGoogleFunctions библиотекиsearchBlockDelay
определяется следующим образом вне@implementation
:статический CGFloat searchBlockDelay = 0.2;
источник
Быстрый взлом будет примерно таким:
- (void)textViewDidChange:(UITextView *)textView { static NSTimer *timer; [timer invalidate]; timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(requestNewDataFromServer) userInfo:nil repeats:NO]; }
Каждый раз, когда текстовое представление изменяется, таймер становится недействительным, поэтому он не срабатывает. Создается новый таймер, который запускается через 1 секунду. Поиск обновляется только после того, как пользователь прекращает печатать в течение 1 секунды.
источник
requestNewDataFromServer
метод должен быть изменен, чтобы получить параметр отuserInfo
scheduledTimer...
.Решение Swift 4 плюс некоторые общие комментарии:
Все это разумные подходы, но если вы хотите образцового поведения автопоиска, вам действительно нужны два отдельных таймера или диспетчера.
Идеальное поведение состоит в том, что 1) автопоиск запускается периодически, но 2) не слишком часто (из-за нагрузки на сервер, пропускной способности сотовой связи и возможности вызвать заикание пользовательского интерфейса) и 3) он запускается быстро, как только возникает пауза в ввод пользователя.
Вы можете добиться такого поведения с помощью одного долгосрочного таймера, который срабатывает, как только начинается редактирование (я предлагаю 2 секунды) и разрешается запускать независимо от последующих действий, плюс один краткосрочный таймер (~ 0,75 секунды), который сбрасывается каждый раз. изменение. По истечении любого таймера запускается автопоиск и сбрасываются оба таймера.
В результате непрерывный набор текста приводит к автоматическому поиску каждые долгие секунды, но пауза гарантированно запускает автопоиск в течение коротких секунд.
Вы можете очень просто реализовать это поведение с помощью класса AutosearchTimer ниже. Вот как им пользоваться:
// The closure specifies how to actually do the autosearch lazy var timer = AutosearchTimer { [weak self] in self?.performSearch() } // Just call activate() after all user activity func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { timer.activate() } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { performSearch() } func performSearch() { timer.cancel() // Actual search procedure goes here... }
AutosearchTimer выполняет свою собственную очистку при освобождении, поэтому нет необходимости беспокоиться об этом в вашем собственном коде. Но не давайте таймеру сильную ссылку на себя, иначе вы создадите контрольный цикл.
В приведенной ниже реализации используются таймеры, но при желании вы можете преобразовать их в операции диспетчеризации.
// Manage two timers to implement a standard autosearch in the background. // Firing happens after the short interval if there are no further activations. // If there is an ongoing stream of activations, firing happens at least // every long interval. class AutosearchTimer { let shortInterval: TimeInterval let longInterval: TimeInterval let callback: () -> Void var shortTimer: Timer? var longTimer: Timer? enum Const { // Auto-search at least this frequently while typing static let longAutosearchDelay: TimeInterval = 2.0 // Trigger automatically after a pause of this length static let shortAutosearchDelay: TimeInterval = 0.75 } init(short: TimeInterval = Const.shortAutosearchDelay, long: TimeInterval = Const.longAutosearchDelay, callback: @escaping () -> Void) { shortInterval = short longInterval = long self.callback = callback } func activate() { shortTimer?.invalidate() shortTimer = Timer.scheduledTimer(withTimeInterval: shortInterval, repeats: false) { [weak self] _ in self?.fire() } if longTimer == nil { longTimer = Timer.scheduledTimer(withTimeInterval: longInterval, repeats: false) { [weak self] _ in self?.fire() } } } func cancel() { shortTimer?.invalidate() longTimer?.invalidate() shortTimer = nil; longTimer = nil } private func fire() { cancel() callback() } }
источник
См. Следующий код, который я нашел в элементах управления какао. Они отправляют запрос асинхронно для получения данных. Возможно, они получают данные с локального компьютера, но вы можете попробовать это с помощью удаленного API. Отправить асинхронный запрос на удаленный API в фоновом потоке. Перейдите по ссылке ниже:
https://www.cocoacontrols.com/controls/jcautocompletingsearch
источник
Мы можем использовать
dispatch_source
+ (void)runBlock:(void (^)())block withIdentifier:(NSString *)identifier throttle:(CFTimeInterval)bufferTime { if (block == NULL || identifier == nil) { NSAssert(NO, @"Block or identifier must not be nil"); } dispatch_source_t source = self.mappingsDictionary[identifier]; if (source != nil) { dispatch_source_cancel(source); } source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, bufferTime * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0); dispatch_source_set_event_handler(source, ^{ block(); dispatch_source_cancel(source); [self.mappingsDictionary removeObjectForKey:identifier]; }); dispatch_resume(source); self.mappingsDictionary[identifier] = source; }
Подробнее о регулировании выполнения блока с помощью GCD
Если вы используете ReactiveCocoa , рассмотрите
throttle
метод наRACSignal
Вот ThrottleHandler в Swift, если вам интересно
источник
Версия Swift 2.0 решения NSTimer:
private var searchTimer: NSTimer? func doMyFilter() { //perform filter here } func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { if let searchTimer = searchTimer { searchTimer.invalidate() } searchTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(MySearchViewController.doMyFilter), userInfo: nil, repeats: false) }
источник