How to write Async SearchBarController
How do you handle the search if you are searching in millions of record?
What happens when a user enters some text to search and realised that he wanted to search something else or mistyped something?
Ideally, when a user changes the text we should stop our search and begin a search with the new text entered by a user.
How should we write such a code in swift to make sure we cancel the search before starting a new search?
OperationQueue is a good way to handle these kinds of situations. So, let’s get dirty our hands to write a piece of code for it.
We need a delegate which provides a set of APIs to use search functionality as below:
protocol SearchControllerDelegate: class {var searchQueue:OperationQueue {get set}var searchResult:[FlickrPhoto] {get set}func searchDisplayController(controller:SearchViewController, searchText:String)func cancelSearch()}
We just wrote a SearchControllerDelegate protocol which has properties and methods as:
Properties:
- searchQueue to perform async search task
- searchResult stores results returned from the search
Methods:
- searchDisplayController(controller:SearchViewController, searchText:String) to start a search with the searchText
- cancelSearch() to cancel the search
Here, how do we use delegate protocol in our ViewController:
extension SearchViewController {func searchDisplayController(controller:SearchViewController, searchText:String) {guard !searchText.isEmpty else {self.searchResult.removeAll()return}self.searchQueue.cancelAllOperations()self.searchQueue.addOperation { [weak self] inDispatchQueue.main.async(execute: { () -> Void inUIApplication.shared.isNetworkActivityIndicatorVisible = true})FlickrDataManager().fetchPhotosForSearchText(searchText: searchText, onCompletion: { (error: NSError?, flickrPhotos: [FlickrPhoto]?) -> Void inDispatchQueue.main.async(execute: { () -> Void inUIApplication.shared.isNetworkActivityIndicatorVisible = false})if error == nil {self?.searchResult = flickrPhotos!} else {self?.searchResult = []if (error!.code == FlickrDataManager.Errors.invalidAccessErrorStatusCode) {DispatchQueue.main.async(execute: { () -> Void inself?.showErrorAlert()})}}DispatchQueue.main.async(execute: { () -> Void inself?.title = searchTextself?.tableView.reloadData()})})}}func cancelSearch() {self.searchQueue.cancelAllOperations()self.searchResult.removeAll()self.title = “”}}
How to connect SearchControllerDelegate to UI:
class SearchViewController: UIViewController, SearchControllerDelegate, UISearchBarDelegate {var searchQueue: OperationQueue = OperationQueue()var searchResult: [FlickrPhoto] = []@IBOutlet weak var searchBar: UISearchBar!@IBOutlet weak var tableView: UITableView!func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {self.cancelSearch()
self.searchDisplayController(controller: self, searchText: searchBar.text!)}func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {self.cancelSearch()}func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {self.searchDisplayController(controller: self, searchText: searchBar.text!)}
A simple example of the async search can be found here.