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