Published on

SwiftUI crypto app — qidiruv matni yordamida ma'lumotlarni filtrlash

Authors

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:

  • searchText o'zgarganda ishlaydi
  • allCoins o'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. 🚀

Buy mea coffee