- Published on
SwiftUI-da Sheet orqali Location Detail View yaratish
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
LocationDetailView — yangi fayl
Navigator-da o'ng tugmani bosib, Views papkasida yangi SwiftUI View fayl yaratamiz — unga LocationDetailView deb nom beramiz.
Bu view — ma'lum bir location haqidagi barcha tafsilotlarni ko'rsatadigan ekran. Shuning uchun, bu view-ni yaratganda, unga location uzatilishi kerak:
struct LocationDetailView: View {
let location: Location
var body: some View {
Text(location.name)
}
}
Preview-ni to'g'rilash uchun, LocationsDataService-dan birinchi locationni olamiz (preview uchun ! bilan explicit unwrap qilamiz — haqiqiy kodda bunday qilmang):
#Preview {
LocationDetailView(location: LocationsDataService.locations.first!)
.environmentObject(LocationsViewModel())
}
ScrollView va asosiy tuzilma
Bu ekranning hammasi ScrollView ichida bo'ladi — chunki ko'p kontent bor va ekranga sig'masligi mumkin:
var body: some View {
ScrollView {
VStack {
imageSection
.shadow(color: .black.opacity(0.3), radius: 20, x: 0, y: 10)
VStack(alignment: .leading, spacing: 16) {
titleSection
Divider()
descriptionSection
Divider()
mapLayer
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
}
.ignoresSafeArea()
.overlay(alignment: .topLeading) {
backButton
}
.background(.ultraThinMaterial)
}
imageSection — rasm karuseli
Bir nechta rasmni swipe qilib ko'rish uchun TabView va PageTabViewStyle-dan foydalanamiz:
private var imageSection: some View {
TabView {
ForEach(location.imageNames, id: \.self) { name in
Image(name)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width)
.clipped()
}
}
.frame(height: 500)
.tabViewStyle(.page)
}
Muhim: .frame(width: UIScreen.main.bounds.width) va .clipped() — keyingi rasm oldingisinining ustiga chiqib kelishining oldini oladi. Agar bu bo'lmasa, swiping paytida g'alati vizual effekt paydo bo'ladi.
Eslatma: iPad uchun moslashtirilganda, bu qatirni keyinroq o'zgartirishimiz kerak bo'ladi, chunki iPad-da bu ekran to'liq kenglikda bo'lmaydi.
titleSection
private var titleSection: some View {
VStack(alignment: .leading, spacing: 8) {
Text(location.name)
.font(.largeTitle)
.fontWeight(.semibold)
Text(location.cityName)
.font(.title3)
.foregroundColor(.secondary)
}
}
Muhim: VStack-ni to'liq kenglikka yoyish uchun .frame(maxWidth: .infinity, alignment: .leading) tashqi VStack-ga qo'llaniladi — aks holda VStack faqat kontent kengligida qoladi.
descriptionSection
private var descriptionSection: some View {
VStack(alignment: .leading, spacing: 16) {
Text(location.description)
.font(.subheadline)
.foregroundColor(.secondary)
if let url = URL(string: location.link) {
Link("Read more on Wikipedia", destination: url)
.font(.headline)
.tint(.blue)
}
}
}
Muhim: URL(string:) optional qaytaradi, shuning uchun if let bilan xavfsiz unwrap qilish kerak. tint(.blue) — havolalar an'anaviy ko'k rangda ko'rinishi uchun (accent color-ga bog'liq bo'lmasligi uchun).
mapLayer — kichik xarita
Location detail ekranida xuddi shu locationning ustida kichik, interaktiv bo'lmagan xarita ko'rsatiladi:
@EnvironmentObject private var vm: LocationsViewModel
private var mapLayer: some View {
Map(coordinateRegion: .constant(MKCoordinateRegion(
center: location.coordinates,
span: vm.mapSpan
)),
annotationItems: [location]) { location in
MapAnnotation(coordinate: location.coordinates) {
LocationMapAnnotationView()
.shadow(radius: 10)
}
}
.aspectRatio(1, contentMode: .fit)
.cornerRadius(30)
.allowsHitTesting(false)
}
Muhim jihatlar:
.constant(...)— bu xarita hech qachon o'zgarmaydi, shuning uchun@Stateyoki@Bindingshart emas.MKCoordinateRegiondoimiy qiymat sifatida uzatiladi.annotationItems: [location]— butun locationlar ro'yxati emas, faqat joriy location — bitta pin ko'rsatiladi..aspectRatio(1, contentMode: .fit)— xaritani kvadrat shaklida ko'rsatish uchun..allowsHitTesting(false)— xaritani touch-dan himoya qiladi. Sheet scroll bo'ladi, ScrollView scroll bo'ladi, xarita ham scroll bo'lsa — foydalanuvchi uchun chalkash bo'lar edi. Shuning uchun xaritani "qotib qo'yamiz".import MapKit— fayl boshida bo'lishi shart.
backButton va material background
Sheet-ni yopish uchun back tugmasi kerak. U scroll view-ning ustida, yuqori chap qismida joylashadi:
private var backButton: some View {
Button {
vm.sheetLocation = nil
} label: {
Image(systemName: "xmark")
.font(.headline)
.padding(16)
.foregroundColor(.primary)
.background(.thickMaterial)
.cornerRadius(10)
.shadow(radius: 4)
.padding()
}
}
vm.sheetLocation = nil— qiymatni nil-ga o'rnatish sheet-ni avtomatik yopadi..thickMaterial— oq fonga o'xshash, ammo ortidagi kontentni biroz ko'rsatuvchi material.
Butun scroll view-ga ham biroz fon kerak:
.background(.ultraThinMaterial)
ViewModel-ga sheetLocation qo'shish
LocationsViewModel-ga location detail sheet-ini boshqaradigan o'zgaruvchi qo'shamiz:
// Show location detail via sheet
@Published var sheetLocation: Location? = nil
Location?— optional, chunkinil= sheet yopiq, qiymat bor = sheet ochiq.- Boshlang'ich qiymat —
nil(ilova ochilganda sheet ko'rinmaydi).
LocationsView-da sheet qo'shish
LocationsView-dagi asosiy ZStack-ga .sheet modifikatorini qo'shamiz:
.sheet(item: $vm.sheetLocation) { location in
LocationDetailView(location: location)
}
.sheet(item:)—itemoptional identifiable ob'ektga bog'lanadi. Qiymatnilbo'lmagan zahoti, sheet avtomatik ochiladi. Sheet yopilganda qiymat avtomatiknilbo'ladi.
LocationPreviewView-da "Learn More" tugmasi
LocationPreviewView-dagi "Learn more" tugmasida:
Button("Learn more") {
vm.sheetLocation = location
}
Bu qiymatni o'rnatish — sheet-ni ochish uchun yetarli.
Xaritadagi MKCoordinateSpan
Location detail view-dagi xarita biroz ko'proq zoom qilingan bo'lishi kerak. Buning uchun LocationsViewModel-dagi mapSpan-ni yangilaymiz yoki alohida span yaratamiz:
// LocationDetailView ichida mapLayer-da:
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
0.01 — kichik span, ya'ni xarita locationga yaqinroq zoom qilingan bo'ladi.
Natija
Endi ilova to'liq ishlaydi:
- Xaritada location-ni bosamiz → LocationPreviewView paydo bo'ladi
- "Learn more" tugmasini bosamiz → LocationDetailView sheet sifatida pastdan yuqoriga chiqadi
- Sheet ichida:
- Rasmlar karuseli (swipe qilib o'tish mumkin)
- Location nomi va shahri
- Tavsif matni
- Wikipedia havolasi
- Kichik, interaktiv bo'lmagan xarita (bitta pin bilan)
- X tugmasi yoki pastga swipe qilish → sheet yopiladi
Barcha ma'lumot LocationsViewModel orqali boshqariladi — yangi ekran ham xuddi shu @EnvironmentObject-dan foydalanadi, shuning uchun hamma narsa doim sinxron.