- Published on
SwiftUI crypto app — tanga tafsilotlari uchun ma'lumot modeli va xizmat yaratish
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
SwiftUI crypto app — tanga tafsilotlari uchun ma'lumot modeli va xizmat yaratish
Oldingi darsda biz DetailView-ga navigatsiya qo'shdik, lekin u faqat tanga nomini ko'rsatmoqda. Bu darsda biz har bir tanga haqida CoinGecko API dan qo'shimcha ma'lumot — tavsif, bloklash vaqti, hashing algoritmi va ko'proq statistika — yuklab olamiz.
1. CoinGecko API — tanga tafsilotlari endpointi
/coins/{id} endpointi har bir tanga uchun ancha batafsil ma'lumot beradi. URL:
https://api.coingecko.com/api/v3/coins/bitcoin?localization=false&tickers=false&market_data=false&community_data=false&developer_data=false&sparkline=false
localization=false — faqat inglizcha ma'lumot, keraksiz barcha ma'lumotlarni ham o'chiramiz.
2. CoinDetailModel yaratish
API javobi katta bo'ladi. Biz faqat kerakli maydonlarni oladi:
import Foundation
struct CoinDetailModel: Codable {
let id: String?
let symbol: String?
let name: String?
let blockTimeInMinutes: Int?
let hashingAlgorithm: String?
let description: Description?
let links: Links?
enum CodingKeys: String, CodingKey {
case id, symbol, name
case blockTimeInMinutes = "block_time_in_minutes"
case hashingAlgorithm = "hashing_algorithm"
case description, links
}
struct Description: Codable {
let en: String? // Faqat inglizcha tavsif
}
struct Links: Codable {
let homepage: [String]?
let subredditURL: String?
enum CodingKeys: String, CodingKey {
case homepage
case subredditURL = "subreddit_url"
}
}
}
Barcha maydonlar
Optional— ba'zi tangalarda ba'zi ma'lumotlar bo'lmasligi mumkin. Bu ilovani xatolikdan himoya qiladi.
3. CoinDetailDataService
services papkasida CoinDetailDataService.swift faylini yaratamiz. Har bir tanga uchun alohida API so'rov yuboriladi:
import Foundation
import Combine
class CoinDetailDataService {
@Published var coinDetails: CoinDetailModel? = nil
private let coin: CoinModel
private var coinDetailSubscription: AnyCancellable?
init(coin: CoinModel) {
self.coin = coin
getCoinDetails() // Servis yaratilishi bilanoq ma'lumot yuklab olindi
}
func getCoinDetails() {
// URL-da tanganing id-si dinamik qo'yiladi
guard let url = URL(string:
"https://api.coingecko.com/api/v3/coins/\(coin.id)?localization=false&tickers=false&market_data=false&community_data=false&developer_data=false&sparkline=false"
) else { return }
coinDetailSubscription = NetworkingManager.download(url: url)
.decode(type: CoinDetailModel.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: NetworkingManager.handleCompletion,
receiveValue: { [weak self] returnedCoinDetails in
self?.coinDetails = returnedCoinDetails
self?.coinDetailSubscription?.cancel()
}
)
}
}
Nima uchun har bir tangasi uchun alohida servis? Asosiy ekranda barcha 250 tanganing tafsilotlari kerak emas. Faqat foydalanuvchi bosgan tanga uchun API so'rov yuborilsa — bu ancha tejamkor.
4. DetailViewModel
core/detail/viewmodels papkasida DetailViewModel.swift:
import Foundation
import Combine
class DetailViewModel: ObservableObject {
@Published var overviewStatistics: [StatisticModel] = []
@Published var additionalStatistics: [StatisticModel] = []
@Published var coinDescription: String? = nil
@Published var websiteURL: String? = nil
@Published var redditURL: String? = nil
private let coin: CoinModel
private let coinDetailService: CoinDetailDataService
private var cancellables = Set<AnyCancellable>()
init(coin: CoinModel) {
self.coin = coin
self.coinDetailService = CoinDetailDataService(coin: coin)
addSubscribers()
}
private func addSubscribers() {
// CoinDetailModel va CoinModel ni birlashtirish -> statistika hosil qilish
coinDetailService.$coinDetails
.combineLatest(Just(coin)) // Coin model o'zgarmaydi, Just wrapper kerak
.map(mapDataToStatistics)
.sink { [weak self] returnedArrays in
self?.overviewStatistics = returnedArrays.overview
self?.additionalStatistics = returnedArrays.additional
}
.store(in: &cancellables)
// Tanga tavsifi va havolalarini ajratish
coinDetailService.$coinDetails
.sink { [weak self] returnedCoinDetails in
self?.coinDescription = returnedCoinDetails?.description?.en?.removingHTMLOccurrences
self?.websiteURL = returnedCoinDetails?.links?.homepage?.first
self?.redditURL = returnedCoinDetails?.links?.subredditURL
}
.store(in: &cancellables)
}
private func mapDataToStatistics(
coinDetailModel: CoinDetailModel?,
coinModel: CoinModel
) -> (overview: [StatisticModel], additional: [StatisticModel]) {
let overview = createOverviewArray(coinModel: coinModel)
let additional = createAdditionalArray(
coinDetailModel: coinDetailModel,
coinModel: coinModel
)
return (overview, additional)
}
// Overview statistikasi: narx, bozor kap, reyting, hajm
private func createOverviewArray(coinModel: CoinModel) -> [StatisticModel] {
let price = coinModel.currentPrice.asCurrencyWith6Decimals()
let priceChange = coinModel.priceChangePercentage24H
let priceStat = StatisticModel(title: "Joriy narx", value: price, percentageChange: priceChange)
let marketCap = "$" + (coinModel.marketCap?.formattedWithAbbreviations() ?? "")
let marketCapChange = coinModel.marketCapChangePercentage24H
let marketCapStat = StatisticModel(title: "Bozor kapitalizatsiyasi", value: marketCap, percentageChange: marketCapChange)
let rank = "\(coinModel.rank)"
let rankStat = StatisticModel(title: "Reyting", value: rank)
let volume = "$" + (coinModel.totalVolume?.formattedWithAbbreviations() ?? "")
let volumeStat = StatisticModel(title: "24 soatlik hajm", value: volume)
return [priceStat, marketCapStat, rankStat, volumeStat]
}
// Qo'shimcha statistika: yuqori/past narx, foiz o'zgarishi, bozor kap o'zgarishi, bloklash vaqti
private func createAdditionalArray(
coinDetailModel: CoinDetailModel?,
coinModel: CoinModel
) -> [StatisticModel] {
let high = coinModel.high24H?.asCurrencyWith6Decimals() ?? "n/a"
let highStat = StatisticModel(title: "24s maksimal narx", value: high)
let low = coinModel.low24H?.asCurrencyWith6Decimals() ?? "n/a"
let lowStat = StatisticModel(title: "24s minimal narx", value: low)
let priceChange = coinModel.priceChange24H?.asCurrencyWith6Decimals() ?? "n/a"
let pricePercentChange = coinModel.priceChangePercentage24H
let priceChangeStat = StatisticModel(
title: "24s narx o'zgarishi",
value: priceChange,
percentageChange: pricePercentChange
)
let marketCapChange = "$" + (coinModel.marketCapChange24H?.formattedWithAbbreviations() ?? "")
let marketCapPercentChange = coinModel.marketCapChangePercentage24H
let marketCapChangeStat = StatisticModel(
title: "24s bozor kap o'zgarishi",
value: marketCapChange,
percentageChange: marketCapPercentChange
)
// Bloklash vaqti — faqat CoinDetailModel-dan keladi
let blockTime = coinDetailModel?.blockTimeInMinutes ?? 0
let blockTimeString = blockTime == 0 ? "n/a" : "\(blockTime)"
let blockStat = StatisticModel(title: "Blok vaqti", value: blockTimeString)
let hashing = coinDetailModel?.hashingAlgorithm ?? "n/a"
let hashingStat = StatisticModel(title: "Hashing algoritmi", value: hashing)
return [highStat, lowStat, priceChangeStat, marketCapChangeStat, blockStat, hashingStat]
}
}
5. DetailView-ga ViewModel ulash
DetailView.swift-ni yangilaymiz:
struct DetailView: View {
@StateObject private var vm: DetailViewModel
init(coin: CoinModel) {
// ViewModel init-da coin kerak, shuning uchun _vm sintaksisi ishlatamiz
_vm = StateObject(wrappedValue: DetailViewModel(coin: coin))
}
var body: some View {
Text("Yuklanyapti...") // Keyingi darsda to'ldiriladi
.navigationTitle(vm.coin.name)
}
}
6. HTML teglarini olib tashlash
CoinGecko tavsiflarida HTML teglari (<p>, <a>) keladi. String+Extensions.swift-ga:
var removingHTMLOccurrences: String {
return self.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression)
}
Natija
Simulatorda ilovani ishga tushiring va biror tangaga bosing. Konsolda ko'rasiz:
// Tanga detali yuklanib keldi — ma'lumotlar bor!
Keyingi darsda biz bu ma'lumotlarni LazyVGrid yordamida ekranda chiroyli ko'rsatamiz. 🚀