Как осуществлять дросселирование (на основе скорости ввода) в iOS UISearchBar?
У меня есть UISearchBar часть UISearchDisplayController, которая используется для отображения результатов поиска как локальных CoreData, так и удаленного API.
То, что я хочу достичь, - это "отсрочка" поиска в удаленном API. В настоящее время для каждого символа, введенного пользователем, отправляется запрос. Но если пользователь набирает особенно быстро, не имеет смысла посылать много запросов: это поможет подождать, пока он перестанет печатать.
Есть ли способ достичь этого?
Считывание documentation предполагает подождать, пока пользователи явно нажимают на поиск, но я не считаю его идеальным в моем случае.
Проблемы с производительностью. Если операции поиска можно выполнить очень быстро, можно обновить результаты поиска, так как пользователь ввода путем реализации метода searchBar: textDidChange: на делегировать объект. Однако, если операция поиска занимает больше времени, вы следует подождать, пока пользователь удалит кнопку "Поиск", прежде чем искать в searchBarSearchButtonClicked: method. Всегда выполнять выполнять операции фонового потока, чтобы избежать блокировки основного нить. Это позволяет вашему приложению реагировать на пользователя во время поиска. и обеспечивает лучший пользовательский интерфейс.
Отправка многих запросов в API не является проблемой локальной производительности, а только для предотвращения слишком высокой скорости запроса на удаленном сервере.
Спасибо
Ответы
Ответ 1
Благодаря этой ссылке, я нашел очень быстрый и чистый подход. По сравнению с Nirmit ответ на него отсутствует "индикатор загрузки", однако он выигрывает с точки зрения количества строк кода и не требует дополнительных элементов управления. Сначала я добавил файл dispatch_cancelable_block.h
в свой проект (из this repo), а затем определил следующую переменную класса: __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];
});
}
Примечания:
Ответ 2
Попробуйте эту магию:
-(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, но вы можете заставить его вызывать любой метод, который вам нравится!
Ответ 3
Для людей, которым это нужно в Swift
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)
}
EDIT: 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")
}
Ответ 4
Быстрый хак будет выглядеть так:
- (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 секунды.
Ответ 5
См. следующий код, который я нашел в элементах управления cocoa. Они отправляют запрос асинхронно для извлечения данных. Может быть, они получают данные из локального, но вы можете попробовать его с помощью удаленного API. Отправьте запрос async на удаленный API в фоновом потоке. Следуйте ссылке ниже:
https://www.cocoacontrols.com/controls/jcautocompletingsearch
Ответ 6
Мы можем использовать 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.
Ответ 7
Версия 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)
}
Ответ 8
Улучшен 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()
имеет параметр отправителя, который является строкой поиска; Таким образом, доступ к его тексту или любому его методу/свойствам был бы доступен с объявлением его как глобального свойства в классе.