로컬 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")
}
답변
향상된 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()
방법은, 검색 창 인 송신기 매개 변수를 갖는다; 따라서 텍스트 또는 메서드 / 속성에 액세스하는 것은 클래스에서 전역 속성으로 선언하여 액세스 할 수 있습니다.
답변
이 링크 덕분에 매우 빠르고 깔끔한 접근 방식을 찾았습니다. 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 솔루션 및 몇 가지 일반적인 설명 :
이들은 모두 합리적인 접근 방식이지만 모범적 인 자동 검색 동작을 원한다면 실제로 두 개의 개별 타이머 또는 디스패치가 필요합니다.
이상적인 동작은 1) 자동 검색이 주기적으로 트리거되지만 2) 너무 자주 (서버로드, 셀룰러 대역폭 및 UI 끊김을 유발할 수있는 가능성으로 인해), 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