imnikita
imnikita

Reputation: 21

Swift tableView.reloadData() does not reload table view in Swift

Trying to reload my tableView after a fetch request, however, it does not work. I have tableView with embedded collectionView in the first tableViewCell. Here is how my collectionView class looks like:

class RegionCell: UITableViewCell, FetchRegionsDelegate {
    
    static let reuseId = "regionCellID"
    var collectionView: UICollectionView!
    var tableViewManager = RegionsAndCountriesTableVC()
    var selectedRegion: String?

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        configureCollectionView()
        tableViewManager.delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureCollectionView() {
        collectionView = UICollectionView(frame: contentView.bounds, collectionViewLayout: createThreeColumnFlowLayout(in: contentView))
        contentView.addSubview(collectionView)
        collectionView.fillSuperview()
        collectionView.delegate   = self
        collectionView.dataSource = self
        collectionView.backgroundColor = .systemBackground
        collectionView.register(RegionCollectionViewCell.self, forCellWithReuseIdentifier: RegionCollectionViewCell.reuseId)
    }
    
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        6
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RegionCollectionViewCell.reuseId, for: indexPath) as! RegionCollectionViewCell
        let region = regions[indexPath.item]
        cell.imageView.image = region.regionImage
        cell.textLabel.text = region.regionName
        if indexPath.row == 0 {
            collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .left)
            cell.isSelected = true
        }
        return cell
    }

    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        tableViewManager.fetchByRegions(regionName: (regions[indexPath.item].regionName).lowercased())
            self.tableViewManager.tableView.reloadData()
    }

when I chose didSelectItemAt, the delegate works and a function with a new API call executes in the UITableViewController, however, even though the API call is successful, the tableView cells remain the same, even after self.tableView.reloadData() is called. Here is how my UITableViewController class looks:

protocol FetchRegionsDelegate {
    var selectedRegion: String? { get set }
}


class RegionsAndCountriesTableVC: UITableViewController {
    
    var countries = [Country]()
    var delegate: FetchRegionsDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CountryCell.self, forCellReuseIdentifier: CountryCell.reuseId)
        tableView.register(RegionCell.self, forCellReuseIdentifier: RegionCell.reuseId)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        fetchInitialData()
    }


    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        section == 0 ? 1 : countries.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.section == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: RegionCell.reuseId, for: indexPath)
            return cell as! RegionCell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: CountryCell.reuseId, for: indexPath) as! CountryCell
            cell.textLabel?.text = countries[indexPath.row].name
            return cell
        }
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        section == 0 ?  "Regions" : "Country"
    }
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        indexPath.section == 0 ? 250 : 45
    }

    // MARK: - FetchRequest
    
    func fetchInitialData() {
        let url = "https://restcountries-v1.p.rapidapi.com/all/?rapidapi-key=APIKey"
        fetchGenericJSONData(urlString: url) { (countries: [Country]? , error) in
            guard let safeCountries = countries else { return }
            self.countries = safeCountries
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }
    
    func fetchByRegions(regionName: String) {
        if regionName == "all" {
            countries.removeAll()
            fetchInitialData()
        } else {
            countries.removeAll()
            print(self.countries.count)
            let url = "https://restcountries-v1.p.rapidapi.com/region/\(regionName)/?rapidapi-key=APIKey"
            fetchGenericJSONData(urlString: url) { (countries: [Country]?, error) in
                guard let safeCountriesByRegion = countries else { return }
                
                self.countries = safeCountriesByRegion
                print(self.countries.count)
                print(self.countries)
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }
    }

}

Could anybody tell me what am I doing wrong here? Thank you in advance!

Upvotes: 0

Views: 1615

Answers (1)

vadian
vadian

Reputation: 285160

Creating new instances of RegionsAndCountriesTableVCin the table view cell which are not related to the parent view controller is not what you want and the reason of the issue.

You need the actual reference to the parent controller.

This can be accomplished pretty simply with a callback closure.


In RegionCell define a callback property, it passes the region name

class RegionCell: UITableViewCell{

   var callback : ((String) -> Void)?
...

and call it instead of the delegate

 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let regionName = regions[indexPath.item].regionName.lowercased()
    callback?(regionName)
 }

Delete the entire code related to protocol/delegate.


In RegionsAndCountriesTableVC assign the callback in cellForRow

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: RegionCell.reuseId, for: indexPath) as! RegionCell
        cell.callback = { regionName in
            self.fetchByRegions(regionName: regionName)
            tableView.reloadData()
        }
        return cell 
    } else {
        let cell = tableView.dequeueReusableCell(withIdentifier: CountryCell.reuseId, for: indexPath) as! CountryCell
        cell.textLabel?.text = countries[indexPath.row].name
        return cell
    }
}

Upvotes: 1

Related Questions