Skip to content

Implementing Search Capabilities with UISearchController

UISearchController is a UIKit class introduced in iOS 8 that provides a standard interface for implementing search functionality within an app. It integrates a search bar with a results view controller, handling the presentation and updating of search results. This document covers how to implement search capabilities using UISearchController in UIKit-based iOS applications.

Purpose

  • Search Interface: Provides a native search bar and results UI for filtering content.
  • Integration: Seamlessly integrates with UIViewController, UITableView, or UICollectionView.
  • User Experience: Supports animated transitions, scope buttons, and search suggestions.

Key Features

  • Search Bar: A customizable UISearchBar for user input.
  • Results Controller: A separate view controller to display search results.
  • Delegate and Data Source: Uses delegates to handle search interactions and update results.
  • Scope Buttons: Supports segmented controls for filtering search results.
  • Automatic Management: Handles keyboard, focus, and UI transitions.

Basic Implementation

To implement search, configure a UISearchController, set its properties, and integrate it into a view controller, typically with a table view to display results.

Example: Searching a Table View

swift
import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating {
    @IBOutlet weak var tableView: UITableView!
    
    let data = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
    var filteredData: [String] = []
    let searchController = UISearchController(searchResultsController: nil)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Setup table view
        tableView.dataSource = self
        tableView.delegate = self
        
        // Setup search controller
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Search Fruits"
        navigationItem.searchController = searchController
        navigationItem.hidesSearchBarWhenScrolling = false
        definesPresentationContext = true
        
        filteredData = data
    }
    
    // MARK: - UITableViewDataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return filteredData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = filteredData[indexPath.row]
        return cell
    }
    
    // MARK: - UISearchResultsUpdating
    func updateSearchResults(for searchController: UISearchController) {
        guard let searchText = searchController.searchBar.text, !searchText.isEmpty else {
            filteredData = data
            tableView.reloadData()
            return
        }
        
        filteredData = data.filter { $0.lowercased().contains(searchText.lowercased()) }
        tableView.reloadData()
    }
}

Key Points

  • Initialization: Create a UISearchController with a searchResultsController (optional; nil uses the same view controller for results).
  • Search Results Updater: Set searchResultsUpdater to a class conforming to UISearchResultsUpdating to handle search input.
  • Integration: Assign the search controller to navigationItem.searchController for navigation bar integration.
  • Background Obscuring: Set obscuresBackgroundDuringPresentation to false to keep the underlying content visible during search.
  • Presentation Context: Set definesPresentationContext = true to ensure the search bar remains in the correct context.

Advanced Features

Using a Separate Results Controller

Display search results in a dedicated view controller:

swift
class SearchResultsController: UITableViewController {
    var filteredData: [String] = []
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return filteredData.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = filteredData[indexPath.row]
        return cell
    }
}

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating {
    // ... other properties as above
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let resultsController = SearchResultsController()
        let searchController = UISearchController(searchResultsController: resultsController)
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = true
        navigationItem.searchController = searchController
        definesPresentationContext = true
    }
    
    func updateSearchResults(for searchController: UISearchController) {
        guard let searchText = searchController.searchBar.text,
              let resultsController = searchController.searchResultsController as? SearchResultsController else {
            return
        }
        
        resultsController.filteredData = data.filter { $0.lowercased().contains(searchText.lowercased()) }
        resultsController.tableView.reloadData()
    }
}

Adding Scope Buttons

Use scope buttons to filter search results by category:

swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    searchController.searchBar.scopeButtonTitles = ["All", "Short", "Long"]
    searchController.searchBar.delegate = self
    // ... other setup
}

// MARK: - UISearchBarDelegate
extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
        filterContent(for: searchBar.text ?? "", scope: selectedScope)
    }
    
    func updateSearchResults(for searchController: UISearchController) {
        let scope = searchController.searchBar.selectedScopeButtonIndex
        filterContent(for: searchController.searchBar.text ?? "", scope: scope)
    }
    
    func filterContent(for searchText: String, scope: Int) {
        filteredData = data.filter { item in
            let matchesSearch = searchText.isEmpty || item.lowercased().contains(searchText.lowercased())
            switch scope {
            case 1: // Short
                return matchesSearch && item.count <= 5
            case 2: // Long
                return matchesSearch && item.count > 5
            default: // All
                return matchesSearch
            }
        }
        tableView.reloadData()
    }
}

Customize the search bar’s appearance and behavior:

swift
searchController.searchBar.tintColor = .systemBlue
searchController.searchBar.searchTextField.backgroundColor = .white
searchController.searchBar.showsCancelButton = true
searchController.searchBar.autocapitalizationType = .none

Handling Search Bar Events

Use UISearchBarDelegate to respond to events like cancel or search button clicks:

swift
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
    filteredData = data
    tableView.reloadData()
}

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    searchBar.resignFirstResponder()
}

Control the search bar’s visibility:

swift
navigationItem.hidesSearchBarWhenScrolling = true // Hide when scrolling

Best Practices

  • Performance: Optimize filtering logic for large datasets to avoid UI lag.
  • Delegate Management: Ensure the searchResultsUpdater and searchBar.delegate are set correctly.
  • Accessibility: Ensure the search bar and results are VoiceOver-compatible.
  • Clear Feedback: Update results in real-time as the user types for a responsive experience.
  • Scope Clarity: Use clear, concise scope button titles if filtering by category.
  • Testing: Test on iPhone and iPad, in various orientations and with different data sizes.
  • Minimal Customization: Avoid excessive search bar styling to maintain system consistency.

Limitations

  • iOS 8+: UISearchController is available only on iOS 8 and later.
  • Single Search Bar: Only one search bar is supported per UISearchController.
  • Results Controller: Using a separate results controller increases complexity for state management.
  • Large Datasets: Real-time filtering may require optimization for performance.

Summary

UISearchController provides a powerful, native solution for implementing search in UIKit apps. By configuring the search bar, handling updates via UISearchResultsUpdating, and optionally using scope buttons or a results controller, developers can create intuitive search experiences. Following best practices ensures performance, accessibility, and a seamless user experience.

Released under the MIT License.