- Published on
SwiftUI Map App — Ro'yxatdan animatsiyali menyu
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
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 = location → didSet → updateMapRegion()) 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'rinadiPlainListStyle()— 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:
| Protokol | Savolga 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 = false → Ro'yxat yopiladi
Sarlavha matni darhol yangilanadi (animatsiyasiz)
Video oxiridagi yangiliklar
Models/
└── Location.swift ← Equatable 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.