UISearchBar не отвечает в UITableView данными JSON

согласно заголовку, UISearchBar не отвечает в UITableView данными JSON. Я не могу заставить работать поле поиска, не могли бы вы мне помочь?

TableView работает нормально, данные отображаются, но когда я ввожу слово в поле поиска, ничего не происходит.

Может проблема в этом расширении? расширение 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()
        }
        
    
    }

person John Smith    schedule 04.12.2020    source источник
comment
filteredArray = dataArray.filter(...) вообще звонили? Если да, то что происходит при вызове tableView(_:, cellForRowA:)? Правильно ли значение shouldShowSearchResults? Правильно ли значение filteredArray (я имею в виду только необходимое значение)?   -  person Larme    schedule 04.12.2020


Ответы (2)


Попробуйте изменить свой код вот так

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 = ""
        }
    }
person Luca Pizzini    schedule 04.12.2020
comment
Привет, Лука, спасибо за помощь. Ваш код отлично работает, спасибо! :-) Мне просто нужно было добавить DispatchQueue.main.async, иначе это дало мне ошибку с симулятором. Просто любопытство, что это для [я без собственности]. - person John Smith; 05.12.2020
comment
Он сохраняет слабую ссылку на экземпляр, на который ссылается, чтобы избежать циклов сильных ссылок между экземплярами и оптимизировать распределение памяти. Использование unowned предполагает, что ссылка всегда будет относиться к выделенному экземпляру. Вы можете использовать [weak self], если считаете, что ссылка может быть равна нулю или освобождена. Для получения дополнительной информации об управлении памятью Swift см. docs.swift.org/swift-book/ LanguageGuide / - person Luca Pizzini; 05.12.2020

Прежде всего, эффективнее объявить отфильтрованный массив с тем же типом, что и массив источника данных.

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

В textDidChange проверьте, пуста ли строка поиска, и установите shouldShowSearchResults соответственно.

А переход на NSString вообще не нужен

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 тоже не нужен.

В cellForRowAt добавьте идентификатор к ячейке и повторно используйте ячейки

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  
}

И имейте в виду, что переход не работает (может даже вылетать), если отображаются результаты поиска.

Более сложное решение - UITableViewDiffableDataSource (iOS 13+).

person vadian    schedule 04.12.2020
comment
Привет, Вадиан, Спасибо за помощь. Ваш код выдает мне сообщение об ошибке: значение типа GalleryData не имеет члена countryText. - person John Smith; 05.12.2020
comment
Я скопировал имя свойства из вашего кода, что явно неверно. Пожалуйста, смотрите редактирование. - person vadian; 05.12.2020