1

I am writing an application to track the cryptocurrency exchange rate. The api has separate url requests for each coin. Here is a JSON response coming from the server, to a request for one coin:

{
    "status": {
        "elapsed": 2,
        "timestamp": "2022-08-23T06:10:16.417580964Z"
    },
    "data": {
        "id": "1e31218a-e44e-4285-820c-8282ee222035",
        "serial_id": 6057,
        "symbol": "BTC",
        "name": "Bitcoin",
        "slug": "bitcoin",
        "contract_addresses": null,
        "_internal_temp_agora_id": "9793eae6-f374-46b4-8764-c2d224429791",
        "market_data": {
            "price_usd": 20946.467798282705,
            "price_btc": 1,
            "price_eth": 13.351682485155417,
            "volume_last_24_hours": 7635594314.553516,
            "real_volume_last_24_hours": 6038552423.10257,
            "volume_last_24_hours_overstatement_multiple": 1.2644742944254175,
            "percent_change_usd_last_1_hour": null,
            "percent_change_btc_last_1_hour": null,
            "percent_change_eth_last_1_hour": null,
            "percent_change_usd_last_24_hours": -2.1478472228280485,
            "percent_change_btc_last_24_hours": 0.11113305637977958,
            "percent_change_eth_last_24_hours": 0.0518833986287626,
            "ohlcv_last_1_hour": null,
            "ohlcv_last_24_hour": null,
            "last_trade_at": "2022-08-23T06:10:15Z"
        }

I need to send several url requests and convert the received responses into a table where each cell is a certain coin corresponding to a certain url request.

I wrote a model and a service layer, but when sending two requests, instead of two cells in the table, I get one cell that displays data from the 1st request, and then abruptly changes to data from the second request.

The code is given below:

Сontroller

final class WalletController: UIViewController {
    
    private let walletTable = UITableView()
    private let service = WalletService()
    private var data: [DataWallet] = []
    private let identifier = "walletCell"
    private var pointSorted = 1
    private let queue = DispatchQueue.main
    
    // MARK: Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpView()
        configData()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        self.navigationController?.setNavigationBarHidden(false, animated: false)
        self.navigationItem.setHidesBackButton(true, animated: true)
    }
    
    // MARK: setUpView
    private func setUpView() {
        
        // NavigationBar
        createCustomNavigationBar()
        
        // LogOutBarButton
        let logOutButton = createCustomButton(titleName: "LogOut", selector: #selector(logOutButtonTapped))
        navigationItem.leftBarButtonItem = logOutButton
        
        // SortedBarButton
        let sortedButton = createCustomButton(titleName: "Sorted", selector: #selector(sortedButtonTapped))
        navigationItem.rightBarButtonItem = sortedButton
        
        // TableView
        walletTable.backgroundColor = #colorLiteral(red: 0.9381344914, green: 0.9331676364, blue: 0.9246369004, alpha: 1)
        walletTable.separatorColor = #colorLiteral(red: 0.1599435508, green: 0.185090214, blue: 0.167404592, alpha: 1)
        walletTable.delegate = self
        walletTable.dataSource = self
        walletTable.register(UITableViewCell.self, forCellReuseIdentifier: identifier)
        view.addSubview(walletTable)
        walletTable.snp.makeConstraints { maker in
            maker.left.top.right.bottom.equalToSuperview().inset(0)
        }
    }
    
    @objc private func logOutButtonTapped() {
        let startController = StartController()
        navigationController?.pushViewController(startController, animated: true)
    }
    
    private func configData() {
        service.addCoin { [weak self] result in
            switch result {
            case .success(let dataBoy):
                self?.data = [dataBoy]
                DispatchQueue.main.async {
                    self?.walletTable.reloadData()
                }
            case.failure(let error):
                print(error)
            }
        }
    }
    
    @objc private func sortedButtonTapped() {
                if pointSorted == 1 {
                    data = data.sorted{ $0.capital < $1.capital }
                    pointSorted = pointSorted - 1
                } else {
                    data = data.sorted{ $0.country < $1.country }
                    pointSorted = pointSorted + 1
                }
                walletTable.reloadData()
    }
}

// MARK: Delegate
extension WalletController: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
}

// MARK: DataSource
extension WalletController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = walletTable.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
        cell.backgroundColor = #colorLiteral(red: 0.9381344914, green: 0.9331676364, blue: 0.9246369004, alpha: 1)
        let coin = data[indexPath.row]
        
        var content = cell.defaultContentConfiguration()
        content.text = coin.symbol
        content.secondaryText = String(coin.market_data.percent_change_usd_last_1_hour ?? 0.0)
        
        cell.contentConfiguration = content
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let coin = data[indexPath.row]
        let infoController = InfoController()
        
        queue.async {
            infoController.firstTextLabel.text = coin.name
            infoController.firstNumberLabel.text = coin.symbol
            
            infoController.secondTextLabel.text = coin.name
            infoController.secondNumberLabel.text = String(coin.market_data.price_btc ?? 0.0)
            
            infoController.thirdTextLabel.text = coin.name
            infoController.thirdNumberLabel.text = String(coin.market_data.price_usd ?? 0.0)
        }
        
        navigationController?.pushViewController(infoController, animated: true)
    }
}

Service layer

final class WalletService {

    func addCoin(completion: @escaping (Result<DataWallet, Error>) -> Void) {
        
        guard let urlBtc = URL(string: "https://data.messari.io/api/v1/assets/btc/metrics") else { return }
        guard let urlEth = URL(string: "https://data.messari.io/api/v1/assets/eth/metrics") else { return }
        
        let taskBtc =  URLSession.shared.dataTask(with: urlBtc) { data, _, error in
            
            if let error = error {
                completion(.failure(error))
            } else if let data = data {
                do {
                    let result = try JSONDecoder().decode(Response.self, from: data)
                    completion(.success(result.data))
                } catch {
                    completion(.failure(error))
                }
            }
        }
        taskBtc.resume()
        
        let taskEth =  URLSession.shared.dataTask(with: urlEth) { data, _, error in
            
            if let error = error {
                completion(.failure(error))
            } else if let data = data {
                do {
                    let result = try JSONDecoder().decode(Response.self, from: data)
                    completion(.success(result.data))
                } catch {
                    completion(.failure(error))
                }
            }
        }
        taskEth.resume()
    }
}

Model

struct Response: Codable {
    let status: Status
    let data: DataWallet
}

struct Status: Codable {
    let elapsed: Int?
    let timestamp: String?
}

struct DataWallet: Codable {
    let id: String?
    let symbol: String?
    let name: String?
    let market_data: MarketData
}

struct MarketData: Codable {
    let price_usd: Double?
    let price_btc: Double?
    let percent_change_usd_last_1_hour: Double?
    let percent_change_btc_last_1_hour: Double?
}

Can you tell me what I'm doing wrong and how to fix this situation?

I will be grateful for any help!

kovalmark
  • 17
  • 4
  • did you checked this link[https://stackoverflow.com/questions/38553092/how-can-i-construct-multiple-http-requests-in-swift](https://stackoverflow.com/questions/38553092/how-can-i-construct-multiple-http-requests-in-swift) –  Aug 23 '22 at 06:32
  • This is a good article, but unfortunately I could not figure it out, because it uses syntax that is not relevant today. Anyway, thanks a lot for the help! – kovalmark Aug 24 '22 at 15:29

1 Answers1

0

Your controlflow is wrong. For now if you call your service addCoin function you are calling the completionhandler twice. Everytime you are calling the completionhandler your data array gets set containing only the newest value:

self?.data = [dataBoy]

The most simple solution here would be to append the data instead of setting it.

self?.data.append(dataBoy)

and you would need to clear [data] at the start of the function.

private func configData() {

    data = [] //<- add this to clear the collection

    service.addCoin { [weak self] result in
        switch result {
        case .success(let dataBoy):
            self?.data.append(dataBoy)
            DispatchQueue.main.async {
                self?.walletTable.reloadData()
            }
        case.failure(let error):
            print(error)
        }
    }
}
burnsi
  • 6,194
  • 13
  • 17
  • 27
  • This is amazing! You saved me! This approach really works. The only thing I did not use [data] = [], and data = [] Now I will think about how to use one request for 10 urls, and not 10 requests for 10 urls :) – kovalmark Aug 24 '22 at 15:33