Published on

SwiftUI Map App β€” Maxsus xarita pinlari (Map Annotations)

Authors

Maxsus xarita pinlari β€” MapAnnotation

Bu videoda xaritaga joy belgilari (pinlar) qo'shiladi. Avval Apple-ning standart pinlari ishlatiladi, keyin ular maxsus LocationMapAnnotationView bilan almashtiriladi.


Map initializer-ini yangilash

Annotatsiyalarni qo'llash uchun Map komponentining yangi initializer-i ishlatiladi:

// Oldingi (annotatsiyasiz)
Map(coordinateRegion: $vm.mapRegion)

// Yangi (annotatsiyali)
Map(
    coordinateRegion: $vm.mapRegion,
    annotationItems: vm.locations
) { location in
    // Har bir joy uchun pin
}

annotationItems β€” Identifiable protokoliga mos bo'lgan har qanday to'plam qabul qiladi. Location allaqachon Identifiable-ga mos, shuning uchun qo'shimcha o'zgarish kerak emas.


Standart pinlar β€” MapMarker

Tez sinab ko'rish uchun Apple-ning standart markeri:

Map(
    coordinateRegion: $vm.mapRegion,
    annotationItems: vm.locations
) { location in
    MapMarker(coordinate: location.coordinates, tint: .blue)
}

Bu ishlaydi, lekin ilovaning umumiy uslubiga mos kelmaydi. Shuning uchun maxsus pin yaratiladi.


LocationMapAnnotationView β€” maxsus pin

Views/ papkasida yangi SwiftUI View β€” LocationMapAnnotationView.swift yaratiladi:

struct LocationMapAnnotationView: View {

    let accentColor = Color("AccentColor")

    var body: some View {
        VStack(spacing: 0) {
            // Asosiy doira icon
            Image(systemName: "map.circle.fill")
                .resizable()
                .scaledToFit()
                .frame(width: 30, height: 30)
                .font(.headline)
                .foregroundColor(.white)
                .padding(6)
                .background(accentColor)
                .cornerRadius(36)

            // Uchburchak β€” o'q ko'rinishida
            Image(systemName: "triangle.fill")
                .resizable()
                .scaledToFit()
                .foregroundColor(accentColor)
                .frame(width: 10, height: 10)
                .rotationEffect(Angle(degrees: 180))
                .offset(y: -3)
                .padding(.bottom, 40)
        }
    }
}

Muhim tafsilotlar:

.cornerRadius(36) β€” doira hosil qilish uchun: frame(30) + padding(6)*2 = 42 β†’ cornerRadius(36) β‰ˆ yarmi β†’ to'liq doira.

.padding(.bottom, 40) β€” xaritada pin-ning markaziy nuqtasi VStack-ning o'rtasi bo'ladi. Pastga 40pt padding qo'shish markazni pastga tushiradi, ya'ni pin tepasiga joylashadi β€” shu tariqa pin aniq joylashuv ustini emas, tepasini ko'rsatadi.

Preview uchun:

struct LocationMapAnnotationView_Previews: PreviewProvider {
    static var previews: some View {
        ZStack {
            Color.black.ignoresSafeArea()
            LocationMapAnnotationView()
        }
    }
}

Maxsus pini xaritaga qo'shish

Map(
    coordinateRegion: $vm.mapRegion,
    annotationItems: vm.locations
) { location in
    MapAnnotation(coordinate: location.coordinates) {
        LocationMapAnnotationView()
            .shadow(radius: 10)
            .scaleEffect(vm.mapLocation == location ? 1 : 0.7)
            .onTapGesture {
                vm.showNextLocation(location: location)
            }
    }
}

.scaleEffect β€” tanlangan pin katta (1.0), qolganlar kichik (0.7). Joy o'zgarganida animatsiya bilan o'lcham almashinadi.

.onTapGesture β€” pin bosilganda vm.showNextLocation(location:) chaqiriladi. Shu bilan menyu, preview karta, xarita va pin β€” barchasi bir vaqtda yangilanadi.


Body-ni tozalash

Xarita kodi va preview stack alohida computed property-larga ajratiladi:

struct LocationsView: View {

    @EnvironmentObject private var vm: LocationsViewModel

    var body: some View {
        ZStack {
            mapLayer

            VStack(spacing: 0) {
                header
                    .padding()
                Spacer()
                locationsPreviewStack
            }
        }
    }
}

extension LocationsView {

    private var mapLayer: some View {
        Map(
            coordinateRegion: $vm.mapRegion,
            annotationItems: vm.locations
        ) { location in
            MapAnnotation(coordinate: location.coordinates) {
                LocationMapAnnotationView()
                    .shadow(radius: 10)
                    .scaleEffect(vm.mapLocation == location ? 1 : 0.7)
                    .onTapGesture {
                        vm.showNextLocation(location: location)
                    }
            }
        }
        .ignoresSafeArea()
    }

    private var locationsPreviewStack: some View {
        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)
                        ))
                }
            }
        }
    }
}

Endi body quyidagicha toza va o'qilishi oson:

var body: some View {
    ZStack {
        mapLayer          // xarita
        VStack(spacing: 0) {
            header        // yuqori menyu
            Spacer()
            locationsPreviewStack  // pastki karta
        }
    }
}

MapMarker va MapAnnotation farqi

MapMarkerMapAnnotation
Ko'rinishApple-ning standart piniIstalgan SwiftUI view
SozlashFaqat tint rangiTo'liq erkin
iOS versiyasiiOS 14+iOS 14+
Ishlatish holatiTez prototipMaxsus dizayn

Video oxiridagi yangiliklar

Views/
β”œβ”€β”€ LocationsView.swift
β”‚   β”œβ”€β”€ mapLayer           ← annotatsiyali Map
β”‚   └── locationsPreviewStack ← ajratilgan
└── LocationMapAnnotationView.swift  ← yangi fayl

Keyingi videoda "Batafsil" tugmasi uchun LocationDetailView β€” rasmlar galereyasi, joy tavsifi va Wikipedia havolasi β€” quriladi.

Buy mea coffee