Ayaan Khan
Ayaan Khan

Reputation: 35

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)
    }
} 

Upvotes: 1

Views: 34

Answers (1)

Dharmesh Kheni
Dharmesh Kheni

Reputation: 71854

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
    }
}

Upvotes: 2

Related Questions