- Published on
SwiftUI Map App — Joy preview kartasi va asimmetrik o'tishlar
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
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.bluefon qo'shiladi — shunda oq chegara va.ultraThinMaterialeffekti 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
))
| Holat | O'tish |
|---|---|
| Yangi karta kirib kelganda | O'ng tomondan siljib kiradi |
| Eski karta chiqib ketganda | Chap 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:
firstIndex(where:)— joriy joy indeksini topadi (Optionalqaytaradi,guard letbilan xavfsiz ochiladi)locations.indices.contains(nextIndex)— keyingi indeks mavjudmi tekshiradi- Agar oxirgi joy bo'lsa — birinchi joyga qaytadi (halqa)
locations[nextIndex]— faqat indeks mavjudligi tasdiqlangandan keyin ishlatiladi (xavfli emas)
Video oxiridagi yangiliklar
Views/
├── LocationsView.swift ← ZStack + 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.