Search code examples
iosswiftuikit

How to add SearchBar to my TableView that has Object array


So I'm trying to add a SearchBar on top of my TableView so when someone searches something It will show that cell, I did some research, and all of the examples were using an array[String] and my array has a data model. If anyone could help me out with this. Thank you

Data Model

struct SurahData: Decodable {
    let data: [Datum]
}


struct Datum: Decodable {
    let number: Int
    let englishName,englishNameTranslation: String
}

View Controller, Each Cell has two labels


import UIKit

class SecondViewController: UIViewController {
    var activityIndicator:UIActivityIndicatorView = UIActivityIndicatorView()
    
    @IBOutlet weak var searchBar: UISearchBar!
    @IBOutlet weak var tableView: UITableView!
    var emptyArray = [Datum]()
    var numberArray = [Int](1...114)
    let gradientLayer = CAGradientLayer()
    override func viewDidLoad() {
        super.viewDidLoad()
        startIndicator()
        activityIndicator.startAnimating()
        activityIndicator.backgroundColor = .white
        callingSurah()
        gradientLayer.frame = view.bounds
        gradientLayer.colors = [
            UIColor.systemRed.cgColor,
            UIColor.systemOrange.cgColor,
        ]
    }
  
    override func viewWillLayoutSubviews() {
        gradientLayer.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
    }
    
    
    func startIndicator() {
        activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
        activityIndicator.style = UIActivityIndicatorView.Style.large
        activityIndicator.center = self.view.center
        self.view.addSubview(activityIndicator)
    }
    
    func callingSurah(){
        let quranURL = "https://api.alquran.cloud/v1/surah"

        guard let convertedURL = URL(string: quranURL) else {return}
        
        URLSession.shared.dataTask(with: convertedURL) { data, response, error in
            if let error = error {
                self.activityIndicator.stopAnimating()
                self.activityIndicator.hidesWhenStopped = true
                print("An error has occured while performing the call \(String(describing: error))")
            }
            
            if let data = data {
                do{
                    let decoder = try JSONDecoder().decode(SurahData.self, from: data)
                    DispatchQueue.main.async{
                        self.activityIndicator.stopAnimating()
                        self.activityIndicator.hidesWhenStopped = true
                        self.emptyArray = decoder.data
                        self.tableView.reloadData()
                        print(self.emptyArray)
                    }
                }
                catch{
                    self.activityIndicator.stopAnimating()
                    self.activityIndicator.hidesWhenStopped = true
        
                    print("Error occured while decoding \(String(describing: error))")
                }
            }
        }
        .resume()
    }
}

extension SecondViewController: UITableViewDelegate, UITableViewDataSource{

    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return emptyArray.count
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "SurahsViewCell", for: indexPath) as? SurahsViewCell else {return
            UITableViewCell()}
        cell.englishName.text = emptyArray[indexPath.row].englishName
        cell.engTrans.text = emptyArray[indexPath.row].englishNameTranslation
        return cell
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "AyahController") as?  AyahController else {return}
        vc.surahNumber = emptyArray[indexPath.row].number
        navigationController?.pushViewController(vc, animated: true)
    }
} 


Solution

  • You need to handle search text with UISearchBarDelegate

    Declare an Array which can store your search results:

    var filteredArr: [Datum]?
    

    Declare Bool to keep track if search is active:

    var isSearchActive = false
    

    Assign delegate in your viewDidLoad method

    searchBar.delegate = self
    

    Add delegate methods

    extension ViewController: UISearchBarDelegate {
        func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
            isSearchActive = true //Make search active
            self.tableView.reloadData()
            return true
        }
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            guard let surahList = self.emptyArray else { return }
            self.filteredArr = searchText.isEmpty ? surahList : surahList.filter {
                ($0.englishName).range(of: searchText, options: .caseInsensitive) != nil //Filter data with user input for englishName
            }
            self.tableView.reloadData()
        }
        
        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            isSearchActive = false //deactivate search
            self.tableView.reloadData()
        }
    }
    

    And you need to update your tableView delegate methods as well

    extension ViewController: UITableViewDelegate, UITableViewDataSource{
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            if isSearchActive {
                return filteredArr?.count ?? 0
            } else {
                return emptyArray?.count ?? 0
            }
            
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
            guard let cell = tableView.dequeueReusableCell(withIdentifier: "SurahsViewCell", for: indexPath) as? SurahsViewCell else {return
                UITableViewCell()}
            
            var product: Datum?
            
            if isSearchActive {
                product = self.filteredArr?[indexPath.row]
            } else {
                product = self.emptyArray?[indexPath.row]
            }
            
            cell.englishName.text = product?.englishName ?? ""
            cell.engTrans.text = product?.englishNameTranslation ?? ""
            return cell
        }
    }
    

    Here is the full code and I made some minor update as well:

    class ViewController: UIViewController {
    
        var activityIndicator:UIActivityIndicatorView = UIActivityIndicatorView()
        
        @IBOutlet weak var searchBar: UISearchBar!
        @IBOutlet weak var tableView: UITableView!
        
        var emptyArray: [Datum]? //Declare it as optional
        let gradientLayer = CAGradientLayer()
        
        var filteredArr: [Datum]?
        var isSearchActive = false
        
        override func viewDidLoad() {
            super.viewDidLoad()
            startIndicator()
            activityIndicator.startAnimating()
            activityIndicator.backgroundColor = .white
            callingSurah()
            gradientLayer.frame = view.bounds
            gradientLayer.colors = [
                UIColor.systemRed.cgColor,
                UIColor.systemOrange.cgColor,
            ]
            
            searchBar.delegate = self
        }
      
        override func viewWillLayoutSubviews() {
            gradientLayer.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
        }
        
        
        func startIndicator() {
            activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
            activityIndicator.style = UIActivityIndicatorView.Style.large
            activityIndicator.center = self.view.center
            self.view.addSubview(activityIndicator)
        }
        
        func callingSurah(){
            let quranURL = "https://api.alquran.cloud/v1/surah"
    
            guard let convertedURL = URL(string: quranURL) else {return}
            
            URLSession.shared.dataTask(with: convertedURL) { data, response, error in
                if let error = error {
                    self.activityIndicator.stopAnimating()
                    self.activityIndicator.hidesWhenStopped = true
                    print("An error has occured while performing the call \(String(describing: error))")
                }
                
                if let data = data {
                    do{
                        let decoder = try JSONDecoder().decode(SurahData.self, from: data)
                        DispatchQueue.main.async{
                            self.activityIndicator.stopAnimating()
                            self.activityIndicator.hidesWhenStopped = true
                            self.emptyArray = decoder.data
                            self.tableView.reloadData()
                        }
                    }
                    catch{
                        self.activityIndicator.stopAnimating()
                        self.activityIndicator.hidesWhenStopped = true
            
                        print("Error occured while decoding \(String(describing: error))")
                    }
                }
            }
            .resume()
        }
    }
    
    extension ViewController: UISearchBarDelegate {
        func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
            isSearchActive = true
            self.tableView.reloadData()
            return true
        }
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            guard let surahList = self.emptyArray else { return }
            self.filteredArr = searchText.isEmpty ? surahList : surahList.filter {
                ($0.englishName).range(of: searchText, options: .caseInsensitive) != nil
            }
            self.tableView.reloadData()
        }
        
        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            isSearchActive = false
            self.tableView.reloadData()
        }
    }
    
    extension ViewController: UITableViewDelegate, UITableViewDataSource{
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            if isSearchActive {
                return filteredArr?.count ?? 0
            } else {
                return emptyArray?.count ?? 0
            }
            
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
            guard let cell = tableView.dequeueReusableCell(withIdentifier: "SurahsViewCell", for: indexPath) as? SurahsViewCell else {return
                UITableViewCell()}
            
            var product: Datum?
            
            if isSearchActive {
                product = self.filteredArr?[indexPath.row]
            } else {
                product = self.emptyArray?[indexPath.row]
            }
            
            cell.englishName.text = product?.englishName ?? ""
            cell.engTrans.text = product?.englishNameTranslation ?? ""
            return cell
        }
    }