Skip to content

Instantly share code, notes, and snippets.

@rafattouqir
Created February 2, 2020 12:19
Show Gist options
  • Save rafattouqir/f3d076a6f3a99f752a4329ae87c2d889 to your computer and use it in GitHub Desktop.
Save rafattouqir/f3d076a6f3a99f752a4329ae87c2d889 to your computer and use it in GitHub Desktop.
Simple RxSwift like Throttle functionality added to UISearchBar and UISearchController
// ThrottledSearchController.swift
// Created by Daniele Margutti on 10/19/2017
// Updated by RAFAT TOUQIR RAFSUN.
import UIKit
class ThrottledSearchController: UISearchController{
// Mark this property as lazy to defer initialization until
// the searchBar property is called.
private lazy var customSearchBar = ThrottledSearchBar()
// Override this property to return your custom implementation.
override var searchBar: UISearchBar { customSearchBar }
}
class ThrottledSearchBar: UISearchBar, UISearchBarDelegate {
/// Throttle engine
private var throttler: Throttler? = nil
/// Throttling interval
var throttlingInterval: Double? = 0 {
didSet {
guard let interval = throttlingInterval else {
self.throttler = nil
return
}
self.throttler = Throttler(seconds: interval)
}
}
/// Event received when cancel is pressed
var onCancel: (() -> (Void))? = nil
/// Event received when a change into the search box is occurred
var onSearch: ((String) -> (Void))? = nil
override init(frame: CGRect) {
super.init(frame: frame)
self.delegate = self
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.delegate = self
}
// Events for UISearchBarDelegate
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
self.onCancel?()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.onSearch?(self.text ?? "")
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard let throttler = self.throttler else {
self.onSearch?(searchText)
return
}
throttler.throttle {
DispatchQueue.main.async {
self.onSearch?(self.text ?? "")
}
}
}
}
class Throttler {
private let queue: DispatchQueue = DispatchQueue.global(qos: .background)
private var job: DispatchWorkItem = DispatchWorkItem(block: {})
private var previousRun: Date = Date.distantPast
private var maxInterval: Double
init(seconds: Double) {
self.maxInterval = seconds
}
func throttle(block: @escaping () -> ()) {
job.cancel()
job = DispatchWorkItem(){ [weak self] in
self?.previousRun = Date()
block()
}
let delay = Date.second(from: previousRun) > maxInterval ? 0 : maxInterval
queue.asyncAfter(deadline: .now() + Double(delay), execute: job)
}
}
private extension Date {
static func second(from referenceDate: Date) -> Double {
return Date().timeIntervalSince(referenceDate).rounded()
}
}
// ViewController.swift
// Created by RAFAT TOUQIR RAFSUN.
import UIKit
class ViewController: UIViewController {
fileprivate let searchController: UISearchController = {
let searchController = ThrottledSearchController(searchResultsController: nil)
(searchController.searchBar as? ThrottledSearchBar)?.throttlingInterval = 0.5
return searchController
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.setupSearchBar()
}
private func setupSearchBar(){
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Here..."
self.navigationItem.searchController = searchController
self.definesPresentationContext = true
(searchController.searchBar as? ThrottledSearchBar)?.onSearch = {(searchText) in
if searchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty{
print(searchText)
}else {
// A valid search (where searchText.count > MIN_CHARS_TO_SEARCH) could be used
print(searchText)
}
}
}
}
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) { }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment