Search code examples
swiftuitableviewuikit

How to make tableview reload correct data?


I am trying to build an app that decodes a JSON with data about cryptocurrencies. After I fetch the data I calculate the daily turnover ratio of each coin and depending on the percentage I put the coin in one out of four lists.

If I change the selectedList property in the viewModel and run the app the tableView loads the appropriate list. But when I change the value of selectedList in runtime nothing happens. The output of the print statement in the selectedList -> didSet changes but the output of the print statement inside the didLoadCoins method in the view that reloads the tableview remains the same as it was when the app started.

What am I doing wrong?

Viewcontroller

class TVMainViewController: UIViewController, TVMainViewControllerViewButtonDelegate {
    
    private let mainView = TVMainViewControllerView()
    private let viewModel = TVMainViewControllerViewModel()
    
    
    
    //MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
      
        navigationController?.isNavigationBarHidden = true
        mainView.delegate = self
        viewModel.delegate = mainView
        setupUI()
        
        
    }
    
    func didTapSelectRangeButton() {
        let destinationVC = TVSelectRangeViewController()
        destinationVC.modalPresentationStyle = .pageSheet
        destinationVC.sheetPresentationController?.detents = [.medium()]
        destinationVC.sheetPresentationController?.prefersGrabberVisible = true
        
        destinationVC.viewModel = viewModel
        
        present(destinationVC, animated: true)
    }
    
    //MARK: - UI Setup
    private func setupUI() {
        ...
    }

view

protocol TVMainViewControllerViewButtonDelegate {
    func didTapSelectRangeButton()    
}


final class TVMainViewControllerView: UIView, TVMainViewControllerViewModelDelegate {
    
    //MARK: - Variables
    private var viewModel = TVMainViewControllerViewModel()
    var delegate: TVMainViewControllerViewButtonDelegate?
    
    //MARK: - Initializers
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        viewModel.delegate = self
        setupTableView()
        setupUI()
        Task {
            await viewModel.decodeData {
                self.viewModel.delegate?.didLoadCoins()
            }

        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    //MARK: - Functions
    private func setupTableView() {
        tableView.register(VTMainTableViewCell.self, forCellReuseIdentifier: Constants.mainTableViewCell)
        tableView.dataSource = viewModel
    }
    
    func didLoadCoins() {
        print(viewModel.selectedList)
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
    
    @objc func selectRange() {
        delegate?.didTapSelectRangeButton()
    }
    
    //MARK: - UI Setup
    private func setupUI() {
       ...
    }
}

viewModel

import UIKit

/// Communicate with the View to notify it that the data are loaded
protocol TVMainViewControllerViewModelDelegate {
    func didLoadCoins()
}

class TVMainViewControllerViewModel: NSObject {
    
    //MARK: - Variables
    private let networkManager = NetworkManager()
    //    private let baseURL = "https://api.coinlore.net/api/tickers/?start=0&limit=10"
    
    private var coinList = [Cryptocurrency]()
    private var liquidityAbove30List = [CryptoLiquidityModel]()
    private var liquidityAbove20List = [CryptoLiquidityModel]()
    private var liquidityAbove10List = [CryptoLiquidityModel]()
    private var liquidityBelow10List = [CryptoLiquidityModel]()
    private var coinLiquidityList = [CryptoLiquidityModel]()
    var delegate: TVMainViewControllerViewModelDelegate?
    
    var selectedList: Int = 0  {
        didSet {
            print(selectedList)
            delegate?.didLoadCoins()
        }
    }
    
    
    //MARK: - Functions
    
    /// Run networking task and fetch data. It makes 5 network call each of which fetches 100 coins
    func decodeData(completion: @escaping () -> Void) async {
        coinList = []
        var page = 0
        
        while page < 200 {
            let baseURL = "https://api.coinlore.net/api/tickers/?start=\(page)&limit=100"
            
            networkManager.execute(url: baseURL) { result in
                switch result {
                case.success(let decodedData):
                    for coin in decodedData.data {
                        self.coinList.append(coin)
                        self.calculateDailyTurnoverRatio(coin: coin)
                        
                        self.delegate?.didLoadCoins()
                    }
                case .failure(let error):
                    print(error)
                }
            }
            page += 100
            
        } //endwhile
        
        completion()
    }
    
    
    /// Calculate liquidity by dividing daily volume/market cap
    /// Creates 4 lists. Each list contains the coins that have the given turnover ration
    func calculateDailyTurnoverRatio(coin: Cryptocurrency) {
        coinLiquidityList = []
        
        
        let coinLiquidity24 = (coin.volume24 / Double(coin.market_cap_usd)!)*100
        let newCoin = CryptoLiquidityModel(cryptocurrency: coin, liquidity24: coinLiquidity24)
        
        switch coinLiquidity24 {
        case 30 ... 100:
            liquidityAbove30List.append(newCoin)
        case 20 ... 29:
            liquidityAbove20List.append(newCoin)
        case 10 ... 19:
            liquidityAbove10List.append(newCoin)
        default:
            liquidityBelow10List.append(newCoin)
        }
        
    }
    
    
}


extension TVMainViewControllerViewModel: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch selectedList {
        case 0:
            return liquidityBelow10List.count
        case 1:
            return liquidityAbove10List.count
        case 2:
            return liquidityAbove20List.count
        case 3:
            return liquidityAbove30List.count
        default:
            return liquidityBelow10List.count        }    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: Constants.mainTableViewCell, for: indexPath) as! VTMainTableViewCell
        
        switch selectedList {
        case 0:
            let coin = liquidityBelow10List[indexPath.row]
            cell.set(coinSymbol: coin.cryptocurrency.name,
                     coinPrice: coin.cryptocurrency.price_usd,
                     change24: coin.cryptocurrency.percent_change_24h,
                     liquidity24: String(format: "%.2f", coin.liquidity24))
        case 1:
            let coin = liquidityAbove10List[indexPath.row]
            cell.set(coinSymbol: coin.cryptocurrency.name,
                     coinPrice: coin.cryptocurrency.price_usd,
                     change24: coin.cryptocurrency.percent_change_24h,
                     liquidity24: String(format: "%.2f", coin.liquidity24))
            
        case 2:
            let coin = liquidityAbove20List[indexPath.row]
            cell.set(coinSymbol: coin.cryptocurrency.name,
                     coinPrice: coin.cryptocurrency.price_usd,
                     change24: coin.cryptocurrency.percent_change_24h,
                     liquidity24: String(format: "%.2f", coin.liquidity24))
            
        case 3:
            let coin = liquidityAbove30List[indexPath.row]
            cell.set(coinSymbol: coin.cryptocurrency.name,
                     coinPrice: coin.cryptocurrency.price_usd,
                     change24: coin.cryptocurrency.percent_change_24h,
                     liquidity24: String(format: "%.2f", coin.liquidity24))
            
        default:
            let coin = liquidityBelow10List[indexPath.row]
            cell.set(coinSymbol: coin.cryptocurrency.name,
                     coinPrice: coin.cryptocurrency.price_usd,
                     change24: coin.cryptocurrency.percent_change_24h,
                     liquidity24: String(format: "%.2f", coin.liquidity24))
            
        }
        
        return cell
        
    }    
}

Solution

  • You are using two different TVMainViewControllerViewModel instances. If it was not intended, I guess that might be one of the causes of this issue. This might be the case that you think the view and the view controller are communicating with the same view model instance but not (since there are two).

    final class TVMainViewControllerView: UIView, TVMainViewControllerViewModelDelegate {
        
        //MARK: - Variables
        private let viewModel: TVMainViewControllerViewModel
        var delegate: TVMainViewControllerViewButtonDelegate?
        
        //MARK: - Initializers
        init(vm: TVMainViewControllerViewModel) {
          super.init(frame: .zero)
          self.viewModel = vm //Allocate view model from initializer.
          self.viewModel.delegate = self
          setupTableView()
          setupUI()
          Task {
                await viewModel.decodeData {
                    self.viewModel.delegate?.didLoadCoins()
                }
          }
        }
       
    
    
    class TVMainViewController: UIViewController, TVMainViewControllerViewButtonDelegate {
        
        private let viewModel = TVMainViewControllerViewModel()
        private lazy var mainView = TVMainViewControllerView(vm: self.viewModel)
        
        
        //MARK: - Lifecycle
        override func viewDidLoad() {
            super.viewDidLoad()
          
            navigationController?.isNavigationBarHidden = true
            mainView.delegate = self
           // viewModel.delegate = mainView // This one is not needed.
            setupUI()
            
            
        }
    

    I just briefly amended your code to show you what I meant by using only one TVMainViewControllerViewModel instance. Please take a look at what dependency injection is and hope this can give you some clues.