iOS UI 검색바에서 검색(타이핑 속도 기준)을 조절하는 방법은?
로컬 CoreData와 원격 API 모두에서 검색 결과를 표시하는 데 사용되는 UI SearchDisplayController의 UI SearchBar 부분이 있습니다.제가 이루고 싶은 것은 원격 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)
}
@objc func reload() {
print("Doing things")
}
향상된 Swift 4+:
이미 준수하고 있다고 가정할 때, 이는 스위프트 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(Target:)를 구현하는 목적은 에 대한 지속적인 호출을 방지하는 것입니다.reload()
"를 마다(c"우"고),reload()
가 추가된 문자 수를 기준으로 3번 호출됩니다).
개선점은 다음과 같습니다.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
LPGoogle Functions 라이브러리의 일부입니다. 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...
}
자동 검색 타이머는 해제 시 자체 정리를 처리하므로 사용자 코드에서 이에 대해 걱정할 필요가 없습니다.하지만 타이머에 자기에 대한 강력한 참조를 주지 않으면 참조 사이클이 생성됩니다.
아래 구현에서는 타이머를 사용하지만, 원한다면 디스패치 작업 측면에서 재캐스팅할 수 있습니다.
// 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()
}
}
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)
}
코코아 컨트롤에서 찾은 다음 코드를 참조해 주시기 바랍니다.그들은 데이터를 가져오기 위해 비동기식으로 요청을 보내고 있습니다.로컬에서 데이터를 얻는 것일 수도 있지만 원격 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를 사용하여 블록 실행 조절에 대한 자세한 내용
Reactive Cocoa를 사용하는 경우,throttle
메소드 온RACSignal
여기 스위프트의 스로틀 핸들러가 있습니다. 관심이 있으신 분은
사용가능DispatchWorkItem
스위프트 4.0 또는 그 이상을 사용합니다.훨씬 더 쉽고 이해가 됩니다.
사용자가 0.25초 동안 입력하지 않았을 때 API 호출을 실행할 수 있습니다.
class SearchViewController: UIViewController, UISearchBarDelegate {
// We keep track of the pending work item as a property
private var pendingRequestWorkItem: DispatchWorkItem?
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// Cancel the currently pending item
pendingRequestWorkItem?.cancel()
// Wrap our request in a work item
let requestWorkItem = DispatchWorkItem { [weak self] in
self?.resultsLoader.loadResults(forQuery: searchText)
}
// Save the new work item and execute it after 250 ms
pendingRequestWorkItem = requestWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250),
execute: requestWorkItem)
}
}
당신은 여기서 그것에 대한 전체 기사를 읽을 수 있습니다.
- 공개자:저는 작가입니다.
바닐라 파운데이션 기반 조절 기능이 필요하다면,
reactive, combine, timer, NS Object cancel, 기타 복잡한 것으로 들어가지 않고 하나의 라이너 API만 원한다면,
스로틀러는 작업을 수행하는 데 적합한 도구일 수 있습니다.
다음과 같이 반응하지 않고 조절을 사용할 수 있습니다.
import Throttler
for i in 1...1000 {
Throttler.go {
print("throttle! > \(i)")
}
}
// throttle! > 1000
import UIKit
import Throttler
class ViewController: UIViewController {
@IBOutlet var button: UIButton!
var index = 0
/********
Assuming your users will tap the button, and
request asyncronous network call 10 times(maybe more?) in a row within very short time nonstop.
*********/
@IBAction func click(_ sender: Any) {
print("click1!")
Throttler.go {
// Imaging this is a time-consuming and resource-heavy task that takes an unknown amount of time!
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
self.index += 1
print("click1 : \(self.index) : \(String(data: data, encoding: .utf8)!)")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
click1!
click1!
click1!
click1!
click1!
click1!
click1!
click1!
click1!
click1!
2021-02-20 23:16:50.255273-0500 iOSThrottleTest[24776:813744]
click1 : 1 : {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
특정 지연 초를 원하는 경우:
import Throttler
for i in 1...1000 {
Throttler.go(delay:1.5) {
print("throttle! > \(i)")
}
}
// throttle! > 1000
스위프트 5.0
에 기반을 둔GSnyder
대답
//
// AutoSearchManager.swift
// BTGBankingCommons
//
// Created by Matheus Gois on 01/10/21.
//
import Foundation
/// Manage two timers to implement a standard auto search 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.
public class AutoSearchManager {
// MARK: - Properties
private let shortInterval: TimeInterval
private let longInterval: TimeInterval
private let callback: (Any?) -> Void
private var shortTimer: Timer?
private var longTimer: Timer?
// MARK: - Lifecycle
public init(
short: TimeInterval = Constants.shortAutoSearchDelay,
long: TimeInterval = Constants.longAutoSearchDelay,
callback: @escaping (Any?) -> Void
) {
shortInterval = short
longInterval = long
self.callback = callback
}
// MARK: - Methods
public func activate(_ object: Any? = nil) {
shortTimer?.invalidate()
shortTimer = Timer.scheduledTimer(
withTimeInterval: shortInterval,
repeats: false
) { [weak self] _ in self?.fire(object) }
if longTimer == nil {
longTimer = Timer.scheduledTimer(
withTimeInterval: longInterval,
repeats: false
) { [weak self] _ in self?.fire(object) }
}
}
public func cancel() {
shortTimer?.invalidate()
longTimer?.invalidate()
shortTimer = nil
longTimer = nil
}
// MARK: - Private methods
private func fire(_ object: Any? = nil) {
cancel()
callback(object)
}
}
// MARK: - Constants
extension AutoSearchManager {
public enum Constants {
/// Auto-search at least this frequently while typing
public static let longAutoSearchDelay: TimeInterval = 2.0
/// Trigger automatically after a pause of this length
public static let shortAutoSearchDelay: TimeInterval = 0.75
}
}
언급URL : https://stackoverflow.com/questions/24330056/how-to-throttle-search-based-on-typing-speed-in-ios-uisearchbar
'programing' 카테고리의 다른 글
어떻게 두 개를 만들겠습니까?어떻게 두 개를 만들겠습니까?겹치기? (0) | 2023.09.20 |
---|---|
needle이 배열인 경우 in_array를 어떻게 사용할 수 있습니까? (0) | 2023.09.20 |
SELECT SUM은 레코드가 없을 때 행을 반환합니다. (0) | 2023.09.20 |
유닉스와 윈도우 모두에서 작동하는 C의 64비트 정수(uint64_t)에 해당하는 아토이는? (0) | 2023.09.20 |
Spring Data JPA: 규격 쿼리 가져오기 조인 만들기 (0) | 2023.09.20 |