Published on

SwiftUI crypto app — lazy yuklash uchun maxsus NavigationLink yaratish

Authors

Tangalar ro'yxatida biror qatorni bossak — o'sha tanga haqida batafsil ma'lumot ekraniga o'tishimiz kerak. Bu darsda biz navigatsiyani qo'shamiz, lekin SwiftUI-ning standart NavigationLink muammosini ko'rib chiqamiz va undan samaraliroq yechim taklif qilamiz.


1. DetailView yaratish

core/detail/views papkasida DetailView.swift faylini yaratamiz:

import SwiftUI

struct DetailView: View {
    let coin: CoinModel

    init(coin: CoinModel) {
        self.coin = coin
        print("DetailView ishga tushdi: \(coin.name)")
    }

    var body: some View {
        Text(coin.name)
    }
}

Avval oddiy usul bilan ulanib ko'ramiz — allCoinsList-dagi har bir qator uchun:

ForEach(vm.allCoins) { coin in
    NavigationLink(destination: DetailView(coin: coin)) {
        CoinRowView(coin: coin, showHoldingsColumn: false)
    }
}

Bu ishlaydi, lekin muammo bor. Simulatorda qurilma konsolini ko'ring:

DetailView ishga tushdi: Bitcoin
DetailView ishga tushdi: Ethereum
DetailView ishga tushdi: Tether
DetailView ishga tushdi: BNB
... (250 ta)

Muammo: NavigationLink ekranda ko'rinadigan har bir qator uchun darhol DetailView-ni yaratadi! Demak biz hech narsaga bosmasdan oldin 250 ta DetailView yaratilib, ularning init kodlari (keyinchalik API so'rovlari ham!) ishlaydi. Bu ilovani sekinlashtiradi.


3. Lazy Navigation — muammo yechimi

NavigationLink-ni orqa fonda yashiramiz va uni @State o'zgaruvchisi orqali boshqaramiz:

HomeView-da:

@State private var selectedCoin: CoinModel? = nil
@State private var showDetailView: Bool = false

body ichida fon sifatida navigatsiya havolasi:

var body: some View {
    ZStack {
        Color.theme.background.ignoresSafeArea()

        VStack { /* Asosiy kontent */ }
    }
    .background(
        // Ko'rinmas NavigationLink — faqat showDetailView=true bo'lganda faollashadi
        NavigationLink(
            destination: DetailLoadingView(coin: $selectedCoin),
            isActive: $showDetailView,
            label: { EmptyView() }
        )
    )
}

Qator bosilganda:

private func segue(coin: CoinModel) {
    selectedCoin = coin
    showDetailView.toggle()
}

Ro'yxatda:

ForEach(vm.allCoins) { coin in
    CoinRowView(coin: coin, showHoldingsColumn: false)
        .onTapGesture {
            segue(coin: coin)
        }
}

4. DetailLoadingView — oraliq yuklash

DetailView endi @Binding qabul qiluvchi DetailLoadingView orqali ochiladi:

struct DetailLoadingView: View {
    @Binding var coin: CoinModel?

    var body: some View {
        ZStack {
            // Faqat tanga mavjud bo'lganda — haqiqiy DetailView ochiladi
            if let coin = coin {
                DetailView(coin: coin)
            }
        }
    }
}

DetailView-ni esa oddiy let bilan:

struct DetailView: View {
    let coin: CoinModel

    init(coin: CoinModel) {
        self.coin = coin
        print("Haqiqiy DetailView: \(coin.name)")
    }

    var body: some View {
        Text(coin.name)
    }
}

5. Nima yaxshilandi?

Endi simulatorda ilovani ishga tushiring va konsolga qarang:

// Ekran yuklanganida — hech narsa chop etilmaydi!

Bitta tangaga bosganda:

Haqiqiy DetailView: Bitcoin

Avval: barcha 250 ta DetailView bir vaqtda yaratilgan edi. Endi: faqat bosilgan tanga uchun, bosilgan vaqtda yaratiladi.

Bu keyinchalik DetailView-da API so'rovi bo'lganida juda muhim bo'ladi — aks holda 250 ta parallel API so'rovi yuborilgan bo'lardi!


6. CoinRowView-ni to'liq bosiladigan qilish

Muammo: qatorning o'rtasida Spacer() bor — u shaffof bo'lib, uni bosib bo'lmaydi. Shunchaki background-ga minimal shaffof rang qo'shamiz:

// CoinRowView.swift
var body: some View {
    HStack { /* ... */ }
    .background(Color.theme.background.opacity(0.001))
    // opacity 0.001 = ko'rinmaydi, lekin bosish uchun uyga ega
}

Nima uchun 0.001? opacity: 0 — element mavjud emas, bosib bo'lmaydi. opacity: 0.001 — ko'rinmaydi lekin ekranda mavjud, shuning uchun .onTapGesture ishlaydi.


Portfolio ro'yxatiga ham

portfolioCoinsList-da ham xuddi shunday:

ForEach(vm.portfolioCoins) { coin in
    CoinRowView(coin: coin, showHoldingsColumn: true)
        .onTapGesture {
            segue(coin: coin)
        }
}

Xulosa

UsulKamchiligiAfzalligi
Standart NavigationLinkBarcha viewlarni oldindan yaratadiTez navigatsiya
Lazy NavigationLinkBiroz ko'proq kodFaqat kerakli view yaratiladi

Hozircha DetailView faqat tanga nomini ko'rsatadi. Keyingi darslarda biz uni to'ldirish bilan shug'ullanamiz. 🚀

Buy mea coffee