- Published on
SwiftUI crypto app — qidiruv matni yordamida ma'lumotlarni filtrlash
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
SwiftUI crypto app — qidiruv matni yordamida ma'lumotlarni filtrlash
Oldingi darsda biz ilovaga qidiruv satrini qo'shdik — u chiroyli ko'rinishda va ilovamizda bosh ekranda joylashgan. Lekin hozircha unda qidirsak hech narsa bo'lmaydi, ro'yxat filtrlanmaydi. Shu darsda biz yozilgan matnga qarab tangalarni filtrlash mantiqini yozamiz.
1. Combine yordamida subscriber qo'shish
Filtrlash kodi ViewModel ichida bo'lishi kerak, chunki ViewModel-da ham qidiruv matni (searchText), ham tangalar ro'yxati (allCoins) mavjud.
HomeViewModel-ning addSubscribers() funksiyasiga yangi subscriber qo'shamiz. Bu subscriber $searchText publisheriga ulanadi — ya'ni har safar foydalanuvchi biror harf yozganda bu subscriber yangi qiymat oladi:
// $searchText — Published o'zgaruvchi, har yangi harf yozilganda qayta ishlaydi
addSubscribers {
$searchText
.sink { [weak self] returnedText in
// returnedText — foydalanuvchi yozgan yangi matn
}
.store(in: &cancellables)
}
2. searchText va allCoins ni birlashtirish (combineLatest)
Faqat searchText-ga ulanish yetarli emas — filtrlash uchun tangalar ro'yxati ham kerak. Shu sababli combineLatest yordamida ikki publisherga bir vaqtda ulanamiz:
combineLatest — ikkita publisher birlashtiriladi, ulardan biri yangilanishi bilanoq ikkala qiymat ham birga keladi.
$searchText
.combineLatest(coinDataService.$allCoins)
// endi ikki chiqish bor: matn va tangalar ro'yxati
.sink { [weak self] (returnedText, returnedCoins) in
self?.allCoins = returnedCoins
}
.store(in: &cancellables)
Bu qo'shilgan subscriber:
searchTexto'zgarganda ishlaydiallCoinso'zgarganda ham ishlaydi
3. map va filterCoins funksiyasi
sink chaqirishdan oldin .map qo'shib, tangalarni filtrlash logikasini yozamiz:
$searchText
.combineLatest(coinDataService.$allCoins)
.map { (text, startingCoins) -> [CoinModel] in
// Agar qidiruv maydoni bo'sh bo'lsa — filtrsiz barcha tangalarni qaytaramiz
guard !text.isEmpty else {
return startingCoins
}
// Katta-kichik harfga sezgirlikni yo'q qilish uchun hammani kichik harfga o'giramiz
let lowercasedText = text.lowercased()
// Har bir tanga uchun: nomi, belgisi yoki id-si mos keladimi tekshiramiz
return startingCoins.filter { coin in
coin.name.lowercased().contains(lowercasedText) ||
coin.symbol.lowercased().contains(lowercasedText) ||
coin.id.lowercased().contains(lowercasedText)
}
}
.sink { [weak self] returnedCoins in
self?.allCoins = returnedCoins
}
.store(in: &cancellables)
Nima uchun lowercased()? Swift-da qidiruv katta-kichik harfga sezgir. Agar foydalanuvchi "Bitcoin" deb yozsa va ma'lumotda "bitcoin" bo'lsa — mos kelmaydi. Ikkalasini ham kichik harfga o'girsak, bu muammo hal bo'ladi.
4. filterCoins ni alohida funksiyaga chiqarish
addSubscribers funksiyasini qisqa va o'qilishi oson saqlash uchun filtrlash logikasini alohida private func-ga chiqaramiz:
private func filterCoins(text: String, coins: [CoinModel]) -> [CoinModel] {
guard !text.isEmpty else {
return coins
}
let lowercasedText = text.lowercased()
return coins.filter { coin in
coin.name.lowercased().contains(lowercasedText) ||
coin.symbol.lowercased().contains(lowercasedText) ||
coin.id.lowercased().contains(lowercasedText)
}
}
Endi subscriberda shunchaki qo'ngirik qilamiz:
$searchText
.combineLatest(coinDataService.$allCoins)
.map(filterCoins) // ✅ qisqa va toza
.sink { [weak self] returnedCoins in
self?.allCoins = returnedCoins
}
.store(in: &cancellables)
Bu yangi subscriber allCoins-ni ham yangilaydi, shuning uchun oldingi individual subscriber endi kerak emas — uni o'chirib tashlang.
5. Simulatorda sinash
Ilovani ishga tushirib "eth" yozsak, nomi yoki belgisida "eth" bo'lgan barcha tangalar (masalan, Ethereum, Tether) ro'yxatda qoladi. "bitcoin" yozsak — faqat Bitcoin bilan bog'liq tangalar ko'rinadi. Filtrlash ishlayapti! 🎉
6. Debounce — samaradorlikni oshirish
Foydalanuvchi tez-tez harf yozganda har bir harfdan keyin yuqoridagi butun filtrlash kodi ishlaydi. Bu uncha muammo bo'lmasa-da, unumdorlikni oshirish uchun debounce qo'shamiz:
debounce — belgilangan vaqt (0.5 soniya) ichida yangi qiymat kelmasa, u vaqtdan keyingina davom etadi. Tez-tez keladigan qiymatlarni "to'playdi" va oxirgisini ishlatadi.
$searchText
.combineLatest(coinDataService.$allCoins)
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) // ⏱ 0.5 soniya kutamiz
.map(filterCoins)
.sink { [weak self] returnedCoins in
self?.allCoins = returnedCoins
}
.store(in: &cancellables)
Endi tez yozsak, so'nggi harf yozilgandan 0.5 soniya o'tgach ro'yxat yangilanadi. Bu ilovani ancha samarali qiladi — har bir harf uchun emas, faqat yozish to'xtagach ishlaydi.
Xulosa
Bu darsda Combine-ning eng kuchli xususiyatlaridan biri bo'lgan combineLatest + map + debounce zanjirini ko'rdik. Faqat bir necha satr kod bilan:
- Qidiruv matni o'zgarganda avtomatik filtrlash
- Tangalar yangilanishi bilan ham filtrlash qayta ishlash
- Tez yozish paytida keraksiz ishlovlardan qochish
"Bu Combine-ning kuchi — subscriber yozasiz, qolganini framework o'zi boshqaradi. Agar buni o'zingiz amalga oshirsangiz, iOS dasturlashda ancha oldinga o'tgan bo'lasiz."
Keyingi darsda biz statistik ma'lumotlar uchun model va view yaratamiz. 🚀