iOS Search bar with Realm Database

The search bar is quite a common feature to have in an iOS app whenever you need to show data in a listview. And today I'm going to demonstrate how to add a search bar using a navigation bar button and filter the listview data which uses a Realm database.

Now first, let's go through the code to add a search bar using a button I'm placing on the right side of the navigation bar.

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate {

    private lazy var dataSearchBar: UISearchBar = {
        let searchBar = UISearchBar(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: 44.0))
        searchBar.placeholder = "Search..."
        return searchBar
    }()
    @IBOutlet weak var tblView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "search"), style: .plain, target: self, action: #selector(showOrHideSearchBar))

        tblView.tableHeaderView = dataSearchBar

        dataSearchBar.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        var contentOffset = tblView.contentOffset
        contentOffset.y = contentOffset.y + (tblView.tableHeaderView?.frame ?? CGRect()).height
        tblView.contentOffset = contentOffset
    }

    var isSearchShown = false

    @objc func showOrHideSearchBar(_ sender: Any) {
        if (isSearchShown) {
            var contentOffset = tblView.contentOffset
            contentOffset.y = contentOffset.y + (tblView.tableHeaderView?.frame ?? CGRect()).height
            tblView.contentOffset = contentOffset
        } else {
            let indexPath = IndexPath(item: 1, section: 0)
            tblView.scrollToRow(at: indexPath, at: .bottom, animated: true)
        }
        isSearchShown = !isSearchShown
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        // To Do
    }
}

Now, if we go through the code, a UISearchBar is initialised in the view controller and is added as a header view for the table view. This is specifically done to show/hide the search bar on top of the table view. You can add a search bar anywhere in the view controller as you like.

And to hide the search bar at the start of the screen, I'm scrolling the table view to the height of the search bar in viewWillAppear. And the method showOrHideSearchBar() handles the duty to show/hide the search bar, which is again adjusting the table view height on the inside.

Now let's get to the part that filters the data for the table view.

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    if (searchText != "") {
        listOfEmployees = realm.objects(Employee.self).filter("name contains[c] %@", searchText)
        tblView.reloadData()
    } else {
        listOfEmployees = realm.objects(Employee.self)
        tblView.reloadData()
    }
}

That's all we'll need, now an interesting thing here to notice is how we use the filter with Realm, the contains[c] filter allows us to search to be case insensitive and the text can be present anywhere in the results string. For example, searching for son will result in listing ["Johnson Jeff", "Williamson", "Son heung min"].

Now the full code looks something like this :

//
//  ViewController.swift
//  SearchBarDemo
//
//  Created by apple on 01/07/23.
//

import UIKit
import RealmSwift

@objcMembers class Employee: Object {
    dynamic var name: String = ""
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate {

    var listOfEmployees: Results<Employee>?

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        self.listOfEmployees?.count ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! tableCell

        cell.lblName.text = self.listOfEmployees?[indexPath.row].name

        return cell
    }


    @IBOutlet weak var tblView: UITableView!

    var realm: Realm = try! Realm()

    private lazy var dataSearchBar: UISearchBar = {
        let searchBar = UISearchBar(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: 44.0))
        searchBar.placeholder = "Search..."
        return searchBar
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "SearchBarDemo"

        self.listOfEmployees = realm.objects(Employee.self)

        self.tblView.dataSource = self
        self.tblView.delegate = self

        navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "search"), style: .plain, target: self, action: #selector(showOrHideSearchBar))

        tblView.tableHeaderView = dataSearchBar

        dataSearchBar.delegate = self

    }

    override func viewWillAppear(_ animated: Bool) {
        var contentOffset = tblView.contentOffset
        contentOffset.y = contentOffset.y + (tblView.tableHeaderView?.frame ?? CGRect()).height
        tblView.contentOffset = contentOffset
    }

    var isSearchShown = false

    @objc func showOrHideSearchBar(_ sender: Any) {
        if (isSearchShown) {
            var contentOffset = tblView.contentOffset
            contentOffset.y = contentOffset.y + (tblView.tableHeaderView?.frame ?? CGRect()).height
            tblView.contentOffset = contentOffset
        } else {
            let indexPath = IndexPath(item: 1, section: 0)
            tblView.scrollToRow(at: indexPath, at: .bottom, animated: true)
        }
        isSearchShown = !isSearchShown
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if (searchText != "") {
            listOfEmployees = realm.objects(Employee.self).filter("name contains[c] %@", searchText)
            tblView.reloadData()
        } else {
            listOfEmployees = realm.objects(Employee.self)
            tblView.reloadData()
        }
    }

}

class tableCell : UITableViewCell
{
    @IBOutlet weak var lblName: UILabel!
}