Published on

SwiftUI Map App — Ro'yxatdan animatsiyali menyu

Authors

Ro'yxatdan animatsiyali menyu yaratish

Bu videoda xarita ustidagi header va unga bosilganda ochiladigan joylar ro'yxati quriladi. Joy tanlanganida xarita animatsiya bilan yangi joyga ko'chadi.


Header — yuqoridagi sarlavha paneli

LocationsView-da ZStack ichiga VStack qo'shiladi. Sarlavha matni vm.mapLocation-dan olinadi:

var body: some View {
    ZStack {
        Map(coordinateRegion: $vm.mapRegion)
            .ignoresSafeArea()

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

            Spacer()
        }
    }
}

Header — alohida computed property

Body-ni toza saqlash uchun header-ni extension ichiga ajratamiz:

extension LocationsView {

    private var header: some View {
        VStack {
            Button(action: vm.toggleLocationsList) {
                Text(vm.mapLocation.name + ", " + vm.mapLocation.cityName)
                    .font(.title2)
                    .fontWeight(.black)
                    .foregroundColor(.primary)
                    .frame(height: 55)
                    .frame(maxWidth: .infinity)
                    .animation(.none, value: vm.mapLocation)
                    .overlay(alignment: .leading) {
                        Image(systemName: "arrow.down")
                            .font(.headline)
                            .foregroundColor(.primary)
                            .padding()
                            .rotationEffect(
                                Angle(degrees: vm.showLocationsList ? 180 : 0)
                            )
                    }
            }

            if vm.showLocationsList {
                LocationsListView()
            }
        }
        .background(.thickMaterial)
        .cornerRadius(10)
        .shadow(color: .black.opacity(0.3), radius: 20, x: 0, y: 15)
    }
}

Muhim tafsilotlar:

  • .background(.thickMaterial) — iOS 15+ da mavjud material effekti: orqa fon xiralashib ko'rinadi
  • .animation(.none, value: vm.mapLocation) — sarlavha matni joy o'zgarganda animatsiyasiz almashinadi (xarita animatsiya bilan ko'chadi, matn esa darhol o'zgaradi)
  • .rotationEffect — menyu ochiqligiga qarab strelka 180° buriladi

ViewModel — ro'yxat holati va funksiyalar

LocationsViewModel-ga yangi o'zgaruvchi va funksiyalar qo'shiladi:

class LocationsViewModel: ObservableObject {

    @Published var locations: [Location] = []
    @Published var mapLocation: Location {
        didSet { updateMapRegion(location: mapLocation) }
    }
    @Published var mapRegion: MKCoordinateRegion = MKCoordinateRegion()
    @Published var showLocationsList: Bool = false  // ← yangi

    let mapSpan = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)

    // ...

    func toggleLocationsList() {
        withAnimation(.easeInOut) {
            showLocationsList.toggle()
        }
    }

    func showNextLocation(location: Location) {
        withAnimation(.easeInOut) {
            mapLocation = location
            showLocationsList = false
        }
    }
}

toggleLocationsList()private emas, chunki view-dan chaqiriladi.

showNextLocation() — joy tanlanganida ikki narsa sodir bo'ladi: xarita ko'chadi (mapLocation = locationdidSetupdateMapRegion()) va ro'yxat yopiladi (showLocationsList = false).


LocationsListView — joylar ro'yxati

Yangi SwiftUI View fayli Views/LocationsListView.swift yaratiladi:

struct LocationsListView: View {

    @EnvironmentObject private var vm: LocationsViewModel

    var body: some View {
        List {
            ForEach(vm.locations) { location in
                Button {
                    vm.showNextLocation(location: location)
                } label: {
                    listRowView(location: location)
                }
                .padding(.vertical, 4)
                .listRowBackground(Color.clear)
            }
        }
        .listStyle(PlainListStyle())
    }
}

extension LocationsListView {

    private func listRowView(location: Location) -> some View {
        HStack {
            if let imageName = location.imageNames.first {
                Image(imageName)
                    .resizable()
                    .scaledToFill()
                    .frame(width: 45, height: 45)
                    .cornerRadius(10)
            }

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

Tafsilotlar:

  • location.imageNames.first — har bir joy uchun birinchi rasm ro'yxatdagi preview sifatida ko'rsatiladi
  • .frame(maxWidth: .infinity, alignment: .leading) — barcha qatorlar bir xil kenglikda, matn chap tomonga tekislangan
  • .listRowBackground(Color.clear) — standart oq fon o'chiriladi, material fon orqali ko'rinadi
  • PlainListStyle() — List-ning standart guruhlangan ko'rinishi o'rniga tekis stil

Location — Equatable protokoli

Sarlavha matni animatsiyasiz almashishi uchun .animation(.none, value: vm.mapLocation) ishlatildi. Bu value parametri Equatable protokolini talab qiladi.

import Foundation
import MapKit

struct Location: Identifiable, Equatable {
    let name: String
    let cityName: String
    let coordinates: CLLocationCoordinate2D
    let description: String
    let imageNames: [String]
    let link: String

    var id: String {
        name + cityName
    }

    // Equatable — ikkita Location bir xilmi yoki yo'q?
    static func == (lhs: Location, rhs: Location) -> Bool {
        lhs.id == rhs.id
    }
}

Equatable nima? — ikkita ob'ektni taqqoslash uchun Swift qanday qoidaga amal qilishini belgilaydi. Bu yerda: agar ikkita Location-ning id-i bir xil bo'lsa, ular bir xil joy hisoblanadi.

Identifiable va Equatable farqi:

ProtokolSavolga javob beradi
Identifiable"Bu ob'ektning noyob ID-si nima?"
Equatable"Bu ikki ob'ekt bir xilmi?"

To'liq ma'lumot oqimi

Foydalanuvchi header-ga bosadi
vm.toggleLocationsList()
showLocationsList = true (animatsiya bilan)
LocationsListView ko'rinadi

Foydalanuvchi joy tanlaydi
vm.showNextLocation(location:)
mapLocation = newLocation  →  didSet  →  updateMapRegion()Xarita ko'chadi
showLocationsList = falseRo'yxat yopiladi
Sarlavha matni darhol yangilanadi (animatsiyasiz)

Video oxiridagi yangiliklar

Models/
└── Location.swiftEquatable qo'shildi

Views/
├── LocationsView.swift   ← header, showLocationsList logikasi
└── LocationsListView.swift ← yangi fayl

ViewModels/
└── LocationsViewModel.swift
    ├── showLocationsList: Bool   ← yangi
    ├── toggleLocationsList()     ← yangi
    └── showNextLocation()        ← yangi

Keyingi videoda xaritaga maxsus pinlar (annotations) va joy kartasi (preview card) qo'shiladi.

Buy mea coffee