skip to Main Content

as per title, UISearchBar is not responding in UITableView with JSON data. I can’t get the search field to work, can you help me please?

The TableView works fine, the data displays it, but when I enter a word in the search field nothing happens.

Maybe the problem could lie within this extension?
extension ViewController: UISearchBarDelegate

import UIKit
  

          struct GalleryData: Decodable {
            
            let localized_name: String
            let primary_attr: String
            let attack_type: String
            let img: String
            
        }
    
    
    
    class ViewController: UIViewController {
        
        
        @IBOutlet weak var tableView: UITableView!
        
        
        var dataArray = [GalleryData]()
        var filteredArray = [String]()
        var shouldShowSearchResults = false
        @IBOutlet weak var searchBar: UISearchBar!
        
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            downloadJSON {
                self.tableView.reloadData()
            }
            
            tableView.delegate = self
            tableView.dataSource = self
            
            searchBar.delegate = self
            searchBar.placeholder = "Search here..."
            
        }
        
        
    }
    
    
    
    
    extension ViewController: UITableViewDelegate, UITableViewDataSource {
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            
            if shouldShowSearchResults {
                return filteredArray.count
            } else {
                return dataArray.count
            }
            
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
            let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
            
            if shouldShowSearchResults {
                cell.textLabel?.text = filteredArray[indexPath.row]
            }
            else {
                cell.textLabel?.text = dataArray[indexPath.row].localized_name.capitalized
            }
            
            
            return cell
            
        }
        
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            performSegue(withIdentifier: "showDetails", sender: self)
        }
        
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if let destination = segue.destination as? DetailViewController {
                destination.galleryDataDetail = dataArray[(tableView.indexPathForSelectedRow?.row)!]
            }
        }
        
        func downloadJSON(completed: @escaping () -> ()) {
            
            let url = URL(string: "https://api.opendota.com/api/heroStats")
            
            URLSession.shared.dataTask(with: url!) { (data, response, error) in
                
                do {
                    self.dataArray = try JSONDecoder().decode([GalleryData].self, from: data!)
                    DispatchQueue.main.async {
                        completed()
                    }
                }
                catch {
                    print("JSON error")
                }
                
            }.resume()
            
        }
        
    }
    
    
    
    
    extension ViewController: UISearchBarDelegate {
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            
            let searchString = searchBar.text
            
            filteredArray = dataArray.filter({ (country) -> Bool in
                                let countryText: NSString = country as NSString
                                return (countryText.range(of: searchString!, options: .caseInsensitive).location) != NSNotFound
                            })
            tableView.reloadData()
                }
            
        
        
        func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
            shouldShowSearchResults = true
            tableView.reloadData()
        }
        
        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            searchBar.text = ""
            shouldShowSearchResults = false
            tableView.reloadData()
        }
        
    
    }

2

Answers


  1. Try to change your code like this

    import UIKit
      
        struct GalleryData: Decodable 
        {    
            let localized_name: String
            let primary_attr: String
            let attack_type: String
            let img: String        
        }
        
        class ViewController: UIViewController 
        {    
            @IBOutlet weak var tableView: UITableView!
            @IBOutlet weak var searchBar: UISearchBar!
            
            var dataArray = [GalleryData]() 
            var filteredArray = [GalleryData]() {
                didSet {
                    tableView.reloadData()
                }
            }
            
            override func viewDidLoad() {
                super.viewDidLoad()
                
                tableView.delegate = self
                tableView.dataSource = self
                
                searchBar.delegate = self
                searchBar.placeholder = "Search here..."
                
                downloadJSON()
            } 
        }
        
        extension ViewController: UITableViewDelegate, UITableViewDataSource {
            
            func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                return filteredArray.count
            }
            
            func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
                
                cell.textLabel?.text = filteredArray[indexPath.row].localized_name.capitalized
                
                return cell            
            }
            
            func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
                performSegue(withIdentifier: "showDetails", sender: self)
            }
            
            override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
                if let destination = segue.destination as? DetailViewController {
                    destination.galleryDataDetail = filteredArray[(tableView.indexPathForSelectedRow?.row)!]
                }
            }
            
            func downloadJSON() {
                let url = URL(string: "https://api.opendota.com/api/heroStats")
                
                URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
                    do {
                        self.dataArray = try JSONDecoder().decode([GalleryData].self, from: data!)
                        self.filteredArray = self.dataArray
                    }
                    catch {
                        print("JSON error")
                    }
                }.resume()
            }
        }
        
        extension ViewController: UISearchBarDelegate 
        {    
            func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
                guard let searchText = searchBar.text else { return } 
    
                if (searchText == "") {
                        filteredArray = dataArray 
                        return
                }
                    
                filteredArray = dataArray.filter { $0.localized_name.uppercased.contains(searchText.uppercased) }
            }
    
            func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
                searchBar.text = ""
            }
        }
    
    Login or Signup to reply.
  2. First of all it’s more efficient to declare the filtered array with the same type as the data source array

    var dataArray = [GalleryData]()
    var filteredArray = [GalleryData]()
    

    In textDidChange check if the search string is empty and set shouldShowSearchResults accordingly.

    And the bridge cast to NSString is not needed at all

    extension ViewController: UISearchBarDelegate {
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            
            if searchText.isEmpty {
                shouldShowSearchResults = false
                filteredArray = []
            } else {
                filteredArray = dataArray.filter{ $0.localized_name.range(of: searchText, options: .caseInsensitive) != nil }
                shouldShowSearchResults = true
            }
            tableView.reloadData()
        }
        
        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            searchBar.text = ""
            shouldShowSearchResults = false
            tableView.reloadData()
        }
    }
    

    searchBarTextDidBeginEditing is not needed either.

    In cellForRowAt add an identifier to the cell and reuse the cells

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
          let cell = tableView.dequeueReusableCell(withIdentifier: "GalleryCell", for: indexPath)
            
          let item = shouldShowSearchResults ? filteredArray[indexPath.row] : dataArray[indexPath.row]         
          cell.textLabel?.text = item.localized_name.capitalized            
          return cell  
    }
    

    And be aware that the segue doesn’t work (can even crash) if the search results are displayed

    A more sophisticated solution is UITableViewDiffableDataSource (iOS 13+)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search