IT박스

iOS UISearchBar에서 검색 (입력 속도 기준)을 제한하는 방법은 무엇입니까?

itboxs 2020. 11. 16. 08:00
반응형

iOS UISearchBar에서 검색 (입력 속도 기준)을 제한하는 방법은 무엇입니까?


로컬 CoreData 및 원격 API의 검색 결과를 표시하는 데 사용되는 UISearchDisplayController의 UISearchBar 부분이 있습니다. 내가 달성하고 싶은 것은 원격 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];
}

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)
 }

이 예제는 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")
}

이 링크 덕분에 매우 빠르고 깔끔한 접근 방식을 찾았습니다. Nirmit의 답변에 비해 "로딩 표시기"가 없지만 코드 줄 수 측면에서 이기고 추가 제어가 필요하지 않습니다. 먼저 dispatch_cancelable_block.h파일을 내 프로젝트 ( 이 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;


빠른 해킹은 다음과 같습니다.

- (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 초 동안 입력을 중지 한 후에 만 ​​업데이트됩니다.


향상된 Swift 4 :

이미 준수하고 있다고 가정하면 UISearchBarDelegate이것은 VivienG의 답변의 향상된 Swift 4 버전입니다 .

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()방법은, 검색 창 인 송신기 매개 변수를 갖는다; 따라서 텍스트 또는 메서드 / 속성에 액세스하는 것은 클래스에서 전역 속성으로 선언하여 액세스 할 수 있습니다.


NSTimer 솔루션의 Swift 2.0 버전 :

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)
}

Swift 4 솔루션 및 몇 가지 일반적인 설명 :

이들은 모두 합리적인 접근 방식이지만 모범적 인 자동 검색 동작을 원한다면 실제로 두 개의 개별 타이머 또는 디스패치가 필요합니다.

이상적인 동작은 1) 자동 검색이 주기적으로 트리거되지만 2) 너무 자주 (서버로드, 셀룰러 대역폭 및 UI 끊김을 유발할 수있는 가능성 때문에), 3) 일시 중지되는 즉시 빠르게 트리거되는 것입니다. 사용자의 입력.

You can achieve this behavior with one longer-term timer that triggers as soon as editing begins (I suggest 2 seconds) and is allowed to run regardless of later activity, plus one short-term timer (~0.75 seconds) that is reset on every change. The expiration of either timer triggers autosearch and resets both timers.

The net effect is that continuous typing yields autosearches every long-period seconds, but a pause is guaranteed to trigger an autosearch within short-period seconds.

You can implement this behavior very simply with the AutosearchTimer class below. Here's how to use it:

// 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...
}

The AutosearchTimer handles its own cleanup when freed, so there's no need to worry about that in your own code. But don't give the timer a strong reference to self or you'll create a reference cycle.

The implementation below uses timers, but you can recast it in terms of dispatch operations if you prefer.

// 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()
    }

}

Please see the following code which i've found on cocoa controls. They are sending request asynchronously to fetch the data. May be they are getting data from local but you can try it with the remote API. Send async request on remote API in background thread. Follow below link:

https://www.cocoacontrols.com/controls/jcautocompletingsearch


We can use 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;
}

More on Throttling a block execution using GCD

If you're using ReactiveCocoa, consider throttle method on RACSignal

Here is ThrottleHandler in Swift in you're interested

참고URL : https://stackoverflow.com/questions/24330056/how-to-throttle-search-based-on-typing-speed-in-ios-uisearchbar

반응형