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
}

annotationItemsIdentifiable 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 = 42cornerRadius(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