[SwiftUI] CryptoApp: Market Statistic Data Binding

Junyoung Park·2022년 11월 3일
0

SwiftUI

목록 보기
82/136
post-thumbnail

Download and display live market data from API | SwiftUI Crypto App #13

CryptoApp: Market Statistic Data Binding

구현 목표

  • API 데이터 패치를 통한 통계 뷰 UI 그리기

구현 태스크

  • 시장 데이터 API 패치 데이터 서비스 클래스 구현
  • 뷰 모델 데이터 서비스의 해당 퍼블리셔 구독
  • 데이터 핸들링

핵심 코드

private func fetchData() {
        guard let url = URL(string: "https://api.coingecko.com/api/v3/global") else { return }
        marketSubscription = NetworkingManager
            .download(with: url)
            .decode(type: GlobalData.self, decoder: JSONDecoder())
            .sink(receiveCompletion: NetworkingManager.handleCompletion,
                  receiveValue: { [weak self] globalData in
                self?.marketData = globalData.data
                self?.marketSubscription?.cancel()
            })
    }
  • API 호출을 통해 데이터 핸들링
  • 기존의 코인 데이터를 패치하는 부분과 동일 로직
 marketDataService
            .$marketData
            .map(mapGlobalMarketData)
            .sink { [weak self] stats in
                self?.statistics = stats
            }
            .store(in: &cancellables)
  • 뷰 모델에서 marketDataSerice 선언
  • 해당 데이터 서비스 클래스가 제공하는 marketData 퍼블리셔 구독
  • 건네받은 통계 데이터 리스트 정보를 매핑한 결과값 리턴
private func mapGlobalMarketData(marketDataModel: MarketDataModel?) -> [StatisticModel] {
        var stats: [StatisticModel] = []
        guard let data = marketDataModel else {
            return stats
        }
        let marketCap = StatisticModel(title: "Market Cap", value: data.marketCap, percentageChange: data.marketCapChangePercentage24HUsd)
        let volume = StatisticModel(title: "24h Volume", value: data.volume)
        let btcDominance = StatisticModel(title: "BTC Dominance", value: data.btcDominance)
        let portfolio = StatisticModel(title: "Portfolio Value", value: "$0.00", percentageChange: 0)
        stats.append(contentsOf: [marketCap, volume, btcDominance, portfolio])
        return stats
    }
  • 건네받은 통계 데이터를 통해 뷰에 표현할 별도의 데이터를 생성

소스 코드

import Foundation

struct GlobalData: Codable {
    let data: MarketDataModel?
}

struct MarketDataModel: Codable {
    let totalMarketCap, totalVolume, marketCapPercentage: [String: Double]
    let marketCapChangePercentage24HUsd: Double
    
    enum CodingKeys: String, CodingKey {
        case totalMarketCap = "total_market_cap"
        case totalVolume = "total_volume"
        case marketCapPercentage = "market_cap_percentage"
        case marketCapChangePercentage24HUsd = "market_cap_change_percentage_24h_usd"
    }
    
    var marketCap: String {
        if let item = totalMarketCap.first(where: {$0.key == "usd"}) {
            return "$" + item.value.formattedWithAbbreviations()        }
        return ""
    }
    
    var volume: String {
        if let item = totalVolume.first(where: {$0.key == "usd"}) {
            return "$" + item.value.formattedWithAbbreviations()
        }
        return ""
    }
    
    var btcDominance: String {
        if let item = marketCapPercentage.first(where: {$0.key == "btc"}) {
            return item.value.asPercentString()
        }
        return ""
    }
}
import Foundation
import Combine

class MarketDataService {
    @Published var marketData: MarketDataModel?
    var marketSubscription: AnyCancellable?
    
    init() {
        fetchData()
    }
    
    private func fetchData() {
        guard let url = URL(string: "https://api.coingecko.com/api/v3/global") else { return }
        marketSubscription = NetworkingManager
            .download(with: url)
            .decode(type: GlobalData.self, decoder: JSONDecoder())
            .sink(receiveCompletion: NetworkingManager.handleCompletion,
                  receiveValue: { [weak self] globalData in
                self?.marketData = globalData.data
                self?.marketSubscription?.cancel()
            })
    }
}
import Foundation
import Combine

class HomeViewModel: ObservableObject {
	...
    @Published var statistics: [StatisticModel] = []
    private let coinDataService = CoinDataService()
    private let marketDataService = MarketDataService()
    private var cancellables = Set<AnyCancellable>()
    init() {
        addSubscriber()
    }
    
    private func addSubscriber() {
        // updates allCoins
        ...
        // updates marketData
        marketDataService
            .$marketData
            .map(mapGlobalMarketData)
            .sink { [weak self] stats in
                self?.statistics = stats
            }
            .store(in: &cancellables)
    }
    
    ...
    
    private func mapGlobalMarketData(marketDataModel: MarketDataModel?) -> [StatisticModel] {
        var stats: [StatisticModel] = []
        guard let data = marketDataModel else {
            return stats
        }
        let marketCap = StatisticModel(title: "Market Cap", value: data.marketCap, percentageChange: data.marketCapChangePercentage24HUsd)
        let volume = StatisticModel(title: "24h Volume", value: data.volume)
        let btcDominance = StatisticModel(title: "BTC Dominance", value: data.btcDominance)
        let portfolio = StatisticModel(title: "Portfolio Value", value: "$0.00", percentageChange: 0)
        stats.append(contentsOf: [marketCap, volume, btcDominance, portfolio])
        return stats
    }
}

구현 화면

[🔥] Bad Response from URL: https://api.coingecko.com/api/v3/global

현재 API 서비스를 제공하는 서버 측 에러로 인해 다운된 상태. 실제 API 호출 대신 가데이터를 리턴하는 형식으로 UI 확인

private func getMockData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
            let totalMarketCap = ["usd": 1054229012066.3087]
            let totalVolumne = ["usd": 126545685500.6336]
            let marketCapPercentage = [
                "btc": 36.998185262325336,
                "eth": 17.681382820092026,
                "usdt": 6.5831939813084235,
                "bnb": 5.1925353562337975,
                "usdc": 4.041233854657529,
                "xrp": 2.1730297151359728,
                "busd": 2.082724887419054,
                "doge": 1.7011800098622947,
                "ada": 1.316983958634922,
                "sol": 1.0798557266066307
            ]
            let marketCapChangePercentage24hUsd = 0.2673890923434678
            let marketData = MarketDataModel(totalMarketCap: totalMarketCap, totalVolume: totalVolumne, marketCapPercentage: marketCapPercentage, marketCapChangePercentage24HUsd: marketCapChangePercentage24hUsd)
            self?.marketData = marketData
        }
    }
  • 가데이터를 보내주는 함수로 해당 데이터 서비스 클래스가 이니셜라이즈될 때 사용
profile
JUST DO IT

0개의 댓글