Published on

SwiftUI Map App — Joy preview kartasi va asimmetrik o'tishlar

Authors

Joy preview kartasi va asimmetrik o'tishlar

Bu videoda xaritaning pastki qismida tanlangan joy haqida qisqa ma'lumot ko'rsatuvchi karta quriladi. Joy o'zgarganida karta o'ngdan kirib, chapdan chiqib ketadi — asymmetric transition bilan.


LocationPreviewView — yangi fayl

Views/ papkasida yangi SwiftUI View — LocationPreviewView.swift yaratiladi. Bu view ma'lum bir joy uchun yaratiladi, shuning uchun initializer-da location parametri bo'ladi:

struct LocationPreviewView: View {

    @EnvironmentObject private var vm: LocationsViewModel
    let location: Location

    var body: some View {
        HStack(alignment: .bottom, spacing: 0) {
            VStack(alignment: .leading, spacing: 16) {
                imageSection
                titleSection
            }

            VStack(spacing: 8) {
                learnMoreButton
                nextButton
            }
        }
        .padding(20)
        .background(
            RoundedRectangle(cornerRadius: 10)
                .fill(.ultraThinMaterial)
                .offset(y: 65)
        )
        .cornerRadius(10)
    }
}

Preview uchun:

struct LocationPreviewView_Previews: PreviewProvider {
    static var previews: some View {
        ZStack {
            Color.blue.ignoresSafeArea()
            LocationPreviewView(location: LocationsDataService.locations.first!)
                .environmentObject(LocationsViewModel())
                .padding()
        }
    }
}

Preview-da ZStack + Color.blue fon qo'shiladi — shunda oq chegara va .ultraThinMaterial effekti ko'rinadi.


Komponentlarni ajratish

Body-ni toza saqlash uchun barcha qismlar extension ichiga chiqariladi:

extension LocationPreviewView {

    // Rasm bo'limi
    private var imageSection: some View {
        ZStack {
            if let imageName = location.imageNames.first {
                Image(imageName)
                    .resizable()
                    .scaledToFill()
                    .frame(width: 100, height: 100)
                    .cornerRadius(10)
            }
        }
        .padding(6)
        .background(Color.white)
        .cornerRadius(10)
    }

    // Nom va shahar bo'limi
    private var titleSection: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(location.name)
                .font(.title2)
                .fontWeight(.bold)

            Text(location.cityName)
                .font(.subheadline)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    // "Batafsil" tugmasi
    private var learnMoreButton: some View {
        Button {
            // keyingi videoda to'ldiriladi
        } label: {
            Text("Batafsil")
                .font(.headline)
                .frame(width: 125, height: 35)
        }
        .buttonStyle(.borderedProminent)
    }

    // "Keyingi" tugmasi
    private var nextButton: some View {
        Button {
            vm.nextButtonPressed()
        } label: {
            Text("Keyingi")
                .font(.headline)
                .frame(width: 125, height: 35)
        }
        .buttonStyle(.bordered)
    }
}

.background effekti: RoundedRectangle fon .offset(y: 65) bilan pastga suriladi — shu tariqa fon rasm o'rtasidan boshlanadi va quyi qismni qoplaydi. .cornerRadius(10) esa fon chegarasini kesib, o'sha joyda ham yumoloq burchak hosil qiladi.


LocationsView-ga preview kartasini qo'shish

LocationsView-dagi VStack ichida Spacer-dan keyin ZStack va ForEach qo'shiladi:

VStack(spacing: 0) {
    header
        .padding()

    Spacer()

    // Joy preview kartasi
    ZStack {
        ForEach(vm.locations) { location in
            if vm.mapLocation == location {
                LocationPreviewView(location: location)
                    .shadow(color: .black.opacity(0.3), radius: 20)
                    .padding()
                    .transition(.asymmetric(
                        insertion: .move(edge: .trailing),
                        removal: .move(edge: .leading)
                    ))
            }
        }
    }
}

Nima uchun ForEach kerak?ZStack + ForEach barcha joylar uchun karta tayyorlaydi, lekin faqat vm.mapLocation == location shart bajarilgan bitta karta ko'rsatiladi. Bu transition animatsiyasining to'g'ri ishlashi uchun zarur — agar shunchaki if bilan bitta karta ko'rsatilsa, transition ishlamaydi.


Asimmetrik o'tish — asymmetric transition

.transition(.asymmetric(
    insertion: .move(edge: .trailing),  // o'ngdan kiradi
    removal: .move(edge: .leading)      // chapga chiqib ketadi
))
HolatO'tish
Yangi karta kirib kelgandaO'ng tomondan siljib kiradi
Eski karta chiqib ketgandaChap tomondan siljib chiqadi

Natija: karta almashganda "chapga siljish" taassurotini beradi — cover flow effektiga o'xshash.

Transition canvas-da yaxshi ko'rinmasligi mumkin — simulyatorda sinash tavsiya etiladi.


nextButtonPressed() — ViewModel-da

func nextButtonPressed() {
    // Joriy joy indeksini toping
    guard let currentIndex = locations.firstIndex(where: { $0 == mapLocation }) else {
        print("Xato: joriy joy indeksi topilmadi")
        return
    }

    // Keyingi indeks
    let nextIndex = currentIndex + 1

    // Keyingi indeks mavjudmi?
    guard locations.indices.contains(nextIndex) else {
        // Oxirgi joy — birinchiga qaytish
        guard let firstLocation = locations.first else { return }
        showNextLocation(location: firstLocation)
        return
    }

    // Keyingi joy
    let nextLocation = locations[nextIndex]
    showNextLocation(location: nextLocation)
}

Xavfsiz indeks tekshiruvi:

  1. firstIndex(where:) — joriy joy indeksini topadi (Optional qaytaradi, guard let bilan xavfsiz ochiladi)
  2. locations.indices.contains(nextIndex) — keyingi indeks mavjudmi tekshiradi
  3. Agar oxirgi joy bo'lsa — birinchi joyga qaytadi (halqa)
  4. locations[nextIndex] — faqat indeks mavjudligi tasdiqlangandan keyin ishlatiladi (xavfli emas)

Video oxiridagi yangiliklar

Views/
├── LocationsView.swiftZStack + ForEach + asymmetric transition
└── LocationPreviewView.swift ← yangi fayl: rasm, nom, ikkita tugma

ViewModels/
└── LocationsViewModel.swift
    └── nextButtonPressed()  ← yangi funksiya

Keyingi videoda xaritaga maxsus pinlar (annotations) qo'shiladi va "Batafsil" tugmasi uchun LocationDetailView quriladi.

Buy mea coffee