如何在iOS UISearchBar中限制搜索(基于打字速度)?

mag*_*gix 68 search objective-c ios swift

我有一个UISearchBis部分的UISearchDisplayController,用于显示本地CoreData和远程API的搜索结果.我想要实现的是远程API搜索的"延迟".目前,对于用户键入的每个字符,发送请求.但是如果用户输入特别快,那么发送许多请求就没有意义:等到他停止输入会有所帮助.有没有办法实现这一目标?

阅读文档建议等到用户明确点击搜索,但我发现在我的情况下并不理想.

性能问题.如果可以非常快速地执行搜索操作,则可以通过在委托对象上实现searchBar:textDidChange:方法来在用户键入时更新搜索结果.但是,如果搜索操作需要更多时间,您应该等到用户点击搜索按钮,然后再在searchBarSearchButtonClicked:方法中开始搜索.始终执行后台线程的搜索操作以避免阻塞主线程.这可以使您的应用在搜索运行时对用户做出响应,并提供更好的用户体验.

向API发送许多请求不是本地性能的问题,而是避免远程服务器上的请求率过高.

谢谢

mal*_*hal 119

试试这个魔法:

-(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];
}
Run Code Online (Sandbox Code Playgroud)

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)
 }
Run Code Online (Sandbox Code Playgroud)

请注意,此示例调用一个名为reload的方法,但您可以调用任何您喜欢的方法!

  • 哇....魔术是对的!不敢相信我之前不知道这件事.谢谢! (2认同)
  • 关于"重新加载"...我不得不考虑它几秒钟......这是指本地方法,它会在用户停止键入0.5秒后实际执行你想要做的事情.可以随意调用该方法,例如searchExecute.谢谢! (2认同)

Viv*_*enG 43

对于在Swift中需要这个的人

保持简单,DispatchWorkItem如下所示:https://stackoverflow.com/a/48666001/308315

或使用旧的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)
}
Run Code Online (Sandbox Code Playgroud)

编辑: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")
}
Run Code Online (Sandbox Code Playgroud)


mag*_*gix 15

感谢这个链接,我发现了一种非常快速和干净的方法.与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]; 
    });
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • loadPlacesAutocompleteForInputLPGoogleFunctions库的一部分
  • searchBlockDelay定义如下@implementation:

    static CGFloat searchBlockDelay = 0.2;


Ahm*_*d F 11

改进的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)
}
Run Code Online (Sandbox Code Playgroud)

实现cancelPreviousPerformRequests(withTarget :)的目的是阻止对reload()搜索栏的每次更改的连续调用(如果键入"abc",则不添加它,reload()将根据添加的字符数调用三次) .

改进是:在reload()方法具有发送器参数是搜索栏; 因此,可以通过将其声明为类中的全局属性来访问其文本或其任何方法/属性.


duc*_*i9y 10

快速黑客就是这样:

- (void)textViewDidChange:(UITextView *)textView
{
    static NSTimer *timer;
    [timer invalidate];
    timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(requestNewDataFromServer) userInfo:nil repeats:NO];
}
Run Code Online (Sandbox Code Playgroud)

每次文本视图更改时,计时器都会失效,导致计时器不会触发.创建一个新计时器并设置为在1秒后触发.只有在用户停止键入1秒后才会更新搜索.

  • 因为在这种方法中永远不会触发定时器,所以我发现这里缺少一行:[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; (2认同)

GSn*_*der 5

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...
}
Run Code Online (Sandbox Code Playgroud)

AutosearchTimer 在被释放时会处理自己的清理工作,因此无需在您自己的代码中担心这一点。但是不要给计时器一个强引用 self 否则你会创建一个引用循环。

下面的实现使用计时器,但如果您愿意,您可以根据调度操作重新转换它。

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

}
Run Code Online (Sandbox Code Playgroud)