Published on

SwiftUI crypto app — Core Data yordamida portfelni saqlash

Authors

SwiftUI crypto app — Core Data yordamida portfelni saqlash

Oldingi darsda portfelni tahrirlash ekranini yaratdik, lekin saqlash logikasi yo'q edi. Bu darsda biz Core Data yordamida foydalanuvchi portfelini doimiy xotiraga saqlaymiz — ilova o'chirib ochilganda ham ma'lumotlar saqlanib qoladi.


1. Core Data Entity yaratish

Xcode-da models papkasida yangi fayl yaratamiz — bu safar tur sifatida Data Model (Core Data bo'limi) tanlaymiz:

Fayl nomi: PortfolioContainer

Ichida bitta entity qo'shamiz:

Entity nomiMaydonTur
PortfolioEntitycoinIdString
amountDouble

Nima uchun faqat id va amount? Ilovani ochganda tanga ma'lumotlari (narx, rasm va boshqalar) API dan qayta yuklab olinadi. Core Data-da faqat qaysi tangadan qancha borligini saqlash yetarli — qolgan ma'lumotlar har doim yangilanib turadi.


2. PortfolioDataService klassi

services papkasida PortfolioDataService.swift faylini yaratamiz:

import Foundation
import CoreData

class PortfolioDataService {

    private let container: NSPersistentContainer
    private let containerName: String = "PortfolioContainer"
    private let entityName: String = "PortfolioEntity"

    @Published var savedEntities: [PortfolioEntity] = []

    init() {
        container = NSPersistentContainer(name: containerName)
        container.loadPersistentStores { _, error in
            if let error = error {
                print("Core Data yuklanmadi: \(error)")
            }
        }
        getPortfolio()  // Ilova ishga tushganda saqlangan ma'lumotlarni olamiz
    }

    // MARK: - Public funksiya

    // Tashqaridan faqat shu funksiya chaqiriladi
    func updatePortfolio(coin: CoinModel, amount: Double) {
        // Bu tanga allaqachon portfelda bormi?
        if let entity = savedEntities.first(where: { $0.coinId == coin.id }) {
            if amount > 0 {
                update(entity: entity, amount: amount)  // Miqdorni yangilash
            } else {
                delete(entity: entity)  // Miqdor 0 bo'lsa — o'chirish
            }
        } else {
            add(coin: coin, amount: amount)  // Yangi tanga qo'shish
        }
    }

    // MARK: - Private funksiyalar

    private func getPortfolio() {
        let request = NSFetchRequest<PortfolioEntity>(entityName: entityName)
        do {
            savedEntities = try container.viewContext.fetch(request)
        } catch {
            print("Portfolio entitylarini olishda xato: \(error)")
        }
    }

    private func add(coin: CoinModel, amount: Double) {
        let entity = PortfolioEntity(context: container.viewContext)
        entity.coinId = coin.id
        entity.amount = amount
        applyChanges()
    }

    private func update(entity: PortfolioEntity, amount: Double) {
        entity.amount = amount
        applyChanges()
    }

    private func delete(entity: PortfolioEntity) {
        container.viewContext.delete(entity)
        applyChanges()
    }

    private func save() {
        do {
            try container.viewContext.save()
        } catch {
            print("Core Data ga saqlashda xato: \(error)")
        }
    }

    // Saqlash + portfolio ro'yxatini qayta yuklab olish
    private func applyChanges() {
        save()
        getPortfolio()  // Har safar saqlangach — to'liq qayta o'qish
    }
}

Nima uchun har safar getPortfolio() chaqiramiz? Core Data-dan to'liq qayta o'qish, savedEntities-ni har doim haqiqiy holat bilan sinxronlashtirib turadi. Bu kichik portfel uchun eng xavfsiz usul.


3. HomeViewModel-ga qo'shish

HomeViewModel.swift-da yangi servis va subscriber:

private let portfolioDataService = PortfolioDataService()

// addSubscribers() ichida yangi subscriber:
func addSubscribers() {
    // ...oldingi subscriberlar...

    // Portfolio subscriber: allCoins + savedEntities -> portfolioCoins
    $allCoins
        .combineLatest(portfolioDataService.$savedEntities)
        .map(mapAllCoinsToPortfolioCoins)
        .sink { [weak self] returnedCoins in
            self?.portfolioCoins = returnedCoins
        }
        .store(in: &cancellables)
}

// allCoins va savedEntities dan portfolioCoins hosil qilish
private func mapAllCoinsToPortfolioCoins(
    allCoins: [CoinModel],
    portfolioEntities: [PortfolioEntity]
) -> [CoinModel] {
    allCoins.compactMap { coin in
        // Bu tanga portfelda bormi?
        guard let entity = portfolioEntities.first(where: { $0.coinId == coin.id }) else {
            return nil  // compactMap nil-ni olib tashlaydi
        }
        // Portfeldagi miqdorni tangging currentHoldings-iga qo'yamiz
        return coin.updateHoldings(amount: entity.amount)
    }
}

4. PortfolioView-dan updatePortfolio chaqirish

saveButtonPressed() funksiyasini yangilaymiz:

private func saveButtonPressed() {
    guard
        let coin = selectedCoin,
        let amount = Double(quantityText)  // String -> Double
    else { return }

    // HomeViewModel orqali Core Data ga yozamiz
    vm.updatePortfolio(coin: coin, amount: amount)

    withAnimation(.easeIn) {
        showCheckmark = true
        removeSelectedCoin()
    }
    UIApplication.shared.endEditing()

    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        withAnimation(.easeOut) {
            self.showCheckmark = false
        }
    }
}

HomeViewModel-da updatePortfolio funksiyasi:

func updatePortfolio(coin: CoinModel, amount: Double) {
    portfolioDataService.updatePortfolio(coin: coin, amount: amount)
}

5. Tanga bosilganda mavjud miqdorni ko'rsatish

Foydalanuvchi allaqachon portfelida tanga borligini bilishi uchun — uni bosganda text field-ga mavjud miqdor yozilishi kerak:

private func updateSelectedCoin(coin: CoinModel) {
    selectedCoin = coin

    // Bu tanga portfelda bormi?
    if let portfolioCoin = vm.portfolioCoins.first(where: { $0.id == coin.id }),
       let amount = portfolioCoin.currentHoldings {
        // Portfeldagi miqdorni text field-ga joylashtiramiz
        quantityText = "\(amount)"
    } else {
        quantityText = ""
    }
}

Tap gestureda:

.onTapGesture {
    withAnimation(.easeIn) {
        updateSelectedCoin(coin: coin)  // selectedCoin = coin o'rniga
    }
}

6. Portfel view-da portfolio tangalarini avval ko'rsatish

Qidiruv bo'sh bo'lganda — portfelidagi tangalarni, qidirayotganda — barcha tangalarni ko'rsatamiz:

ForEach(vm.searchText.isEmpty ? vm.portfolioCoins : vm.allCoins) { coin in
    CoinLogoView(coin: coin)
        .frame(width: 75)
        // ...
}

Natija

Ilovada test qiling:

  1. Tanga tanlang → miqdor kiriting → SAQLASH bosing
  2. Xmark bilan yoping → portfel tabida tanga paydo bo'ldi!
  3. Ilovani yoping va qayta oching → portfel saqlanib qoldi ✅

"Bir satr kod (vm.updatePortfolio(coin:amount:)) bilan butun Core Data logikasi ishga tushadi. View-da logika yo'q — hamma narsa xizmatlar va ViewModel-da. Bu MVVM arxitekturasining chinakam kuchi!"

Keyingi darsda biz portfel qiymatini statistika bo'limida to'g'ri hisoblash va API ma'lumotlarini yangilash tugmasini yaratamiz. 🚀

Buy mea coffee