- Published on
SwiftUI crypto app — MVVM va animatsiyalar bilan ro'yxatni saralash
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
SwiftUI crypto app — MVVM va animatsiyalar bilan ro'yxatni saralash
Hozircha tangalar bozor kapitalizatsiyasi bo'yicha tartibda keladi. Bu darsda foydalanuvchiga tangalarni reyting, narx va hisoblar bo'yicha o'sish yoki kamayish tartibida saralash imkonini beramiz. Barcha mantiq HomeViewModel-da bo'ladi — view esa faqat bir o'zgaruvchini yangilaydi.
1. SortOption enum
HomeViewModel.swift faylining yuqorisida enum yaratamiz:
enum SortOption {
case rank // Reytingdan 1-dan katta (default)
case rankReversed // Reytingdan katta-dan kichikka
case holdings // Hisoblardan katta-dan kichikka
case holdingsReversed
case price // Narxdan katta-dan kichikka
case priceReversed
}
Va Published o'zgaruvchi:
@Published var sortOption: SortOption = .holdings // Ilova boshida — hisoblar bo'yicha
2. filterAndSortCoins funksiyasi
Filtrash bilan saralashni bir funksiyada birlashtirish uchun yangi filterAndSortCoins yaratamiz:
private func filterAndSortCoins(text: String, coins: [CoinModel], sort: SortOption) -> [CoinModel] {
// Avval filtrlash
var filteredCoins = filterCoins(text: text, coins: coins)
// Keyin saralash (in-out yordamida yangi massiv yaratilmaydi)
sortCoins(sort: sort, coins: &filteredCoins)
return filteredCoins
}
Subscriber-ni yangilaymiz — $sortOption-ni ham qo'shamiz:
$searchText
.combineLatest(coinDataService.$allCoins, $sortOption)
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)
.map(filterAndSortCoins)
.sink { [weak self] returnedCoins in
self?.allCoins = returnedCoins
}
.store(in: &cancellables)
3. sortCoins funksiyasi — inout bilan samarali saralash
private func sortCoins(sort: SortOption, coins: inout [CoinModel]) {
switch sort {
case .rank, .holdings:
// Reyting: 1-eng past son — eng avval
coins.sort { $0.rank < $1.rank }
case .rankReversed, .holdingsReversed:
coins.sort { $0.rank > $1.rank }
case .price:
// Narx: eng yuqori narx — avval
coins.sort { $0.currentPrice > $1.currentPrice }
case .priceReversed:
coins.sort { $0.currentPrice < $1.currentPrice }
}
}
inoutnima? Oddiy parametr — funksiya ichida nusxa oladi, asl massivni o'zgartirmaydi.inout— asl massivni o'zida saralaydi, yangi massiv yaratmaydi. Bu samaradorlikni oshiradi.Chaqirish vaqtida
&belgisi ishlatiladi:sortCoins(sort: sort, coins: &filteredCoins)
4. Portfolio tangalarini ham saralash
portfolioCoins subscriber qo'shimcha saralashni talab qiladi — faqat holdings holatida:
private func sortPortfolioCoinsIfNeeded(coins: [CoinModel]) -> [CoinModel] {
// Faqat holdings bo'yicha saralash kerak bo'lganda ishlaydi
switch sortOption {
case .holdings:
return coins.sorted { $0.currentHoldingsValue > $1.currentHoldingsValue }
case .holdingsReversed:
return coins.sorted { $0.currentHoldingsValue < $1.currentHoldingsValue }
default:
return coins // Boshqa saralash allCoins-da qilingan
}
}
Subscriber-da:
$allCoins
.combineLatest(portfolioDataService.$savedEntities)
.map(mapAllCoinsToPortfolioCoins)
.sink { [weak self] returnedCoins in
guard let self = self else { return }
self.portfolioCoins = self.sortPortfolioCoinsIfNeeded(coins: returnedCoins)
}
.store(in: &cancellables)
Nima uchun bu subscriberda
$sortOption-ni qo'shmaymiz? AgarsortOptiono'zgarganda bu ham ishlaydigan qilsak,allCoinsham yangilanadi — bu esa portfolio subscriberni ikki marta ishlatadi. Bundan qochish uchun faqat oxirida qo'shimcha saralash qilamiz.
5. Ustun sarlavhalariga animatsiyali o'q qo'shish
HomeView-ning columnTitles ichida har bir ustunni bosiladigan qilamiz:
private var columnTitles: some View {
HStack {
// TANGA ustuni
HStack(spacing: 4) {
Text("Tanga")
Image(systemName: "chevron.down")
// Faqat rank bo'yicha saralanadigan bo'lsa ko'rinadi
.opacity(vm.sortOption == .rank || vm.sortOption == .rankReversed ? 1.0 : 0.0)
// Rank bo'lsa — pastga, rankReversed bo'lsa — yuqoriga
.rotationEffect(Angle(degrees: vm.sortOption == .rank ? 0 : 180))
}
.onTapGesture {
withAnimation(.default) {
// Ternary operator: agar rank bo'lsa — rankReversed, boshqa holat — rank
vm.sortOption = vm.sortOption == .rank ? .rankReversed : .rank
}
}
Spacer()
// HISOBLAR ustuni (faqat portfel tabida)
if showPortfolio {
HStack(spacing: 4) {
Text("Hisoblar")
Image(systemName: "chevron.down")
.opacity(vm.sortOption == .holdings || vm.sortOption == .holdingsReversed ? 1.0 : 0.0)
.rotationEffect(Angle(degrees: vm.sortOption == .holdings ? 0 : 180))
}
.onTapGesture {
withAnimation(.default) {
vm.sortOption = vm.sortOption == .holdings ? .holdingsReversed : .holdings
}
}
}
// NARX ustuni
HStack(spacing: 4) {
Text("Narx")
Image(systemName: "chevron.down")
.opacity(vm.sortOption == .price || vm.sortOption == .priceReversed ? 1.0 : 0.0)
.rotationEffect(Angle(degrees: vm.sortOption == .price ? 0 : 180))
}
.frame(width: UIScreen.main.bounds.width / 3.5, alignment: .trailing)
.onTapGesture {
withAnimation(.default) {
vm.sortOption = vm.sortOption == .price ? .priceReversed : .price
}
}
// Yangilash tugmasi
Button {
withAnimation(.linear(duration: 2.0)) {
vm.reloadData()
}
} label: {
Image(systemName: "goforward")
}
.rotationEffect(Angle(degrees: vm.isLoading ? 360 : 0), anchor: .center)
}
.font(.caption)
.foregroundColor(Color.theme.secondaryText)
.padding(.horizontal)
}
6. Simulatorda sinash
Ilovani ishga tushiring:
- Portfel tabida: "Hisoblar" ni bossangiz — eng katta hisoblar yuqorida ko'rinadi
- O'q animatsiyasi: o'q qarama-qarshi tomonga buriladi
- Narx bo'yicha: asosiy tabda "Narx" ni bossangiz — tartib o'zgaradi
- Tanga: "Tanga" ni bossangiz — CoinGecko reytingi bo'yicha
"Faqat bitta
sortOptiono'zgaruvchisini o'zgartiramiz — Combine barcha subscriberlarni avtomatik ishga tushiradi va hamma narsa yangilanadi. Bu SwiftUI + Combine kombinatsiyasining sehrli tomonlari!"
Keyingi darsda biz batafsil ekran (DetailView) ga navigatsiyani qo'shamiz. 🚀