Подтвердить что ты не робот

Как осуществлять дросселирование (на основе скорости ввода) в iOS UISearchBar?

У меня есть UISearchBar часть UISearchDisplayController, которая используется для отображения результатов поиска как локальных CoreData, так и удаленного API. То, что я хочу достичь, - это "отсрочка" поиска в удаленном API. В настоящее время для каждого символа, введенного пользователем, отправляется запрос. Но если пользователь набирает особенно быстро, не имеет смысла посылать много запросов: это поможет подождать, пока он перестанет печатать. Есть ли способ достичь этого?

Считывание documentation предполагает подождать, пока пользователи явно нажимают на поиск, но я не считаю его идеальным в моем случае.

Проблемы с производительностью. Если операции поиска можно выполнить очень быстро, можно обновить результаты поиска, так как пользователь ввода путем реализации метода searchBar: textDidChange: на делегировать объект. Однако, если операция поиска занимает больше времени, вы следует подождать, пока пользователь удалит кнопку "Поиск", прежде чем искать в searchBarSearchButtonClicked: method. Всегда выполнять выполнять операции фонового потока, чтобы избежать блокировки основного нить. Это позволяет вашему приложению реагировать на пользователя во время поиска. и обеспечивает лучший пользовательский интерфейс.

Отправка многих запросов в API не является проблемой локальной производительности, а только для предотвращения слишком высокой скорости запроса на удаленном сервере.

Спасибо

4b9b3361

Ответ 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]; 
    });
}

Примечания:

  • loadPlacesAutocompleteForInput является частью LPGoogleFunctions библиотеки
  • searchBlockDelay определяется следующим образом вне @implementation:

    статический CGFloat searchBlockDelay = 0.2;

Ответ 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() имеет параметр отправителя, который является строкой поиска; Таким образом, доступ к его тексту или любому его методу/свойствам был бы доступен с объявлением его как глобального свойства в классе.