āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ MapKit SwiftUI āđƒāļ™āļ›āļĩ 2026: āļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļ›āļĢāļ°āļāļ­āļš āļāļēāļĢāļ‹āđ‰āļ­āļ™āļ—āļąāļš āđāļĨāļ°āļāļēāļĢāļĢāļ°āļšāļļāļ•āļģāđāļŦāļ™āđˆāļ‡

āđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļ MapKit āļ”āđ‰āļ§āļĒ SwiftUI āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS: āļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļ›āļĢāļ°āļāļ­āļšāđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡ āļāļēāļĢāļ‹āđ‰āļ­āļ™āļ—āļąāļš āļāļēāļĢāļĢāļ°āļšāļļāļ•āļģāđāļŦāļ™āđˆāļ‡ āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļŠāļ–āļēāļ™āļ—āļĩāđˆ āđāļĨāļ°āļĢāļđāļ›āđāļšāļšāļāļēāļĢāļœāļŠāļēāļ™āļĢāļ§āļĄāļāļąāļš Maps

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ MapKit SwiftUI āļŠāļģāļŦāļĢāļąāļšāļ™āļąāļāļžāļąāļ’āļ™āļē iOS

āđāļœāļ™āļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļ„āļļāļ“āļŠāļĄāļšāļąāļ•āļīāļŦāļĨāļąāļāđƒāļ™āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™ iOS āļŦāļĨāļēāļĒāļ•āļąāļ§ āđ„āļ”āđ‰āđāļāđˆ āļāļēāļĢāļˆāļąāļ”āļŠāđˆāļ‡ āļŸāļīāļ•āđ€āļ™āļŠ āļ­āļŠāļąāļ‡āļŦāļēāļĢāļīāļĄāļ—āļĢāļąāļžāļĒāđŒ āđāļĨāļ°āđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒāļŠāļąāļ‡āļ„āļĄāļ•āļēāļĄāļ•āļģāđāļŦāļ™āđˆāļ‡ āļ•āļąāđ‰āļ‡āđāļ•āđˆ iOS 17 āđ€āļ›āđ‡āļ™āļ•āđ‰āļ™āļĄāļē MapKit āđ„āļ”āđ‰āļˆāļąāļ”āđ€āļ•āļĢāļĩāļĒāļĄ API SwiftUI āđāļšāļšāđ€āļ™āļ—āļĩāļŸāļ—āļĩāđˆāļ—āļģāđƒāļŦāđ‰āļāļēāļĢāļœāļŠāļēāļ™āļĢāļ§āļĄāđāļœāļ™āļ—āļĩāđˆāļ‡āđˆāļēāļĒāļ‚āļķāđ‰āļ™āļ­āļĒāđˆāļēāļ‡āļĄāļēāļ āļœāļđāđ‰āļŠāļĢāļĢāļŦāļēāļšāļļāļ„āļĨāļēāļāļĢāļ›āļĢāļ°āđ€āļĄāļīāļ™āļ—āļąāļāļĐāļ°āđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āđ€āļ›āđ‡āļ™āļ›āļĢāļ°āļˆāļģāđƒāļ™āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„

āđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļ‚āļ­āļ‡āļ„āļđāđˆāļĄāļ·āļ­

āđāļ•āđˆāļĨāļ°āļ„āļģāļ–āļēāļĄāļŠāļ°āļ—āđ‰āļ­āļ™āļĢāļđāļ›āđāļšāļšāļ‚āļ­āļ‡āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„āļˆāļĢāļīāļ‡ āļžāļĢāđ‰āļ­āļĄāļ„āļģāļ•āļ­āļšāđ‚āļ”āļĒāļĨāļ°āđ€āļ­āļĩāļĒāļ”āđāļĨāļ°āđ‚āļ„āđ‰āļ”āļ—āļĩāđˆāđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰ āđāļ™āļ§āļ„āļīāļ”āļ”āļģāđ€āļ™āļīāļ™āđ„āļ›āļˆāļēāļāļĢāļ°āļ”āļąāļšāļžāļ·āđ‰āļ™āļāļēāļ™āļ–āļķāļ‡āļĢāļ°āļ”āļąāļšāļŠāļđāļ‡

āļžāļ·āđ‰āļ™āļāļēāļ™āļ‚āļ­āļ‡āđāļœāļ™āļ—āļĩāđˆāđƒāļ™ SwiftUI

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 1: āļˆāļ°āđāļŠāļ”āļ‡āđāļœāļ™āļ—āļĩāđˆāļžāļ·āđ‰āļ™āļāļēāļ™āļ”āđ‰āļ§āļĒ SwiftUI āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļ•āļąāđ‰āļ‡āđāļ•āđˆ iOS 17 āđ€āļ›āđ‡āļ™āļ•āđ‰āļ™āļĄāļē SwiftUI āđ„āļ”āđ‰āļˆāļąāļ”āđ€āļ•āļĢāļĩāļĒāļĄāļ„āļ­āļĄāđ‚āļžāđ€āļ™āļ™āļ•āđŒ Map āđāļšāļšāđ€āļ™āļ—āļĩāļŸāļ—āļĩāđˆāđāļ—āļ™āļ—āļĩāđˆāđāļ™āļ§āļ—āļēāļ‡ UIViewRepresentable āđāļšāļšāđ€āļ”āļīāļĄ Map view āļĒāļ­āļĄāļĢāļąāļšāļ•āļģāđāļŦāļ™āđˆāļ‡āļ‚āļ­āļ‡āļāļĨāđ‰āļ­āļ‡āđāļĨāļ°āđ€āļ™āļ·āđ‰āļ­āļŦāļēāļ—āļĩāđˆāļˆāļ°āđāļŠāļ”āļ‡

BasicMapView.swiftswift
import SwiftUI
import MapKit

struct BasicMapView: View {
    // Camera position controlling center and zoom
    @State private var cameraPosition: MapCameraPosition = .automatic

    var body: some View {
        // Basic map without additional content
        Map(position: $cameraPosition)
    }
}

// Map centered on a specific region
struct CenteredMapView: View {
    // Defines a region centered on Paris
    @State private var cameraPosition: MapCameraPosition = .region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
            // Span defines the zoom level (in degrees)
            span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
        )
    )

    var body: some View {
        Map(position: $cameraPosition) {
            // Map content (markers, annotations, etc.)
        }
        .mapStyle(.standard) // Map style: standard, imagery, hybrid
    }
}

āļ„āļļāļ“āļŠāļĄāļšāļąāļ•āļī position āđƒāļŠāđ‰ MapCameraPosition āļ‹āļķāđˆāļ‡āđ€āļ›āđ‡āļ™ enum āļ—āļĩāđˆāļ­āļ™āļļāļāļēāļ•āđƒāļŦāđ‰āļĄāļĩāđ‚āļŦāļĄāļ”āļ•āđˆāļēāļ‡ āđ† āđ„āļ”āđ‰āđāļāđˆ .automatic (āļ›āļĢāļąāļšāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī), .region (āļžāļ·āđ‰āļ™āļ—āļĩāđˆāļ„āļ‡āļ—āļĩāđˆ), .camera (āļ„āļ§āļšāļ„āļļāļĄāđ€āļ•āđ‡āļĄāļ—āļĩāđˆ) āļŦāļĢāļ·āļ­ .userLocation (āļ­āļĒāļđāđˆāļ•āļĢāļ‡āļāļĨāļēāļ‡āļ—āļĩāđˆāļœāļđāđ‰āđƒāļŠāđ‰)

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 2: āļĄāļĩāļĢāļđāļ›āđāļšāļšāđāļœāļ™āļ—āļĩāđˆāđƒāļ”āļšāđ‰āļēāļ‡āđƒāļŦāđ‰āđƒāļŠāđ‰?

MapKit āļĄāļĩāļĢāļđāļ›āđāļšāļšāđāļœāļ™āļ—āļĩāđˆāļŦāļĨāļēāļĒāļĢāļđāļ›āđāļšāļšāļ—āļĩāđˆāđ€āļŦāļĄāļēāļ°āļŠāļĄāļāļąāļšāļāļĢāļ“āļĩāļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™āļ•āđˆāļēāļ‡ āđ† Modifier .mapStyle() āļāļģāļŦāļ™āļ”āļĢāļđāļ›āļĨāļąāļāļĐāļ“āđŒāđ€āļŠāļīāļ‡āļ āļēāļžāļ‚āļ­āļ‡āđāļœāļ™āļ—āļĩāđˆ

MapStyles.swiftswift
import SwiftUI
import MapKit

struct MapStylesDemo: View {
    @State private var position: MapCameraPosition = .automatic
    @State private var selectedStyle: MapStyleOption = .standard

    var body: some View {
        VStack {
            Map(position: $position)
                .mapStyle(selectedStyle.style)
                .frame(height: 400)

            // Style selector
            Picker("Style", selection: $selectedStyle) {
                ForEach(MapStyleOption.allCases) { option in
                    Text(option.name).tag(option)
                }
            }
            .pickerStyle(.segmented)
            .padding()
        }
    }
}

enum MapStyleOption: String, CaseIterable, Identifiable {
    case standard
    case imagery
    case hybrid
    case standardElevated
    case imageryElevated

    var id: String { rawValue }

    var name: String {
        switch self {
        case .standard: return "Standard"
        case .imagery: return "Satellite"
        case .hybrid: return "Hybrid"
        case .standardElevated: return "Terrain"
        case .imageryElevated: return "Satellite 3D"
        }
    }

    var style: MapStyle {
        switch self {
        case .standard:
            // Classic road map
            return .standard
        case .imagery:
            // Satellite view without labels
            return .imagery
        case .hybrid:
            // Satellite with roads and names
            return .hybrid
        case .standardElevated:
            // Standard with 3D terrain
            return .standard(elevation: .realistic)
        case .imageryElevated:
            // Satellite with 3D terrain
            return .imagery(elevation: .realistic)
        }
    }
}
āļˆāļļāļ”āļ—āļĩāđˆāļ™āđˆāļēāļŠāļ™āđƒāļˆ

āļĢāļđāļ›āđāļšāļš .standard āļĒāļ­āļĄāļĢāļąāļšāļžāļēāļĢāļēāļĄāļīāđ€āļ•āļ­āļĢāđŒ pointsOfInterest āđ€āļžāļ·āđˆāļ­āļāļĢāļ­āļ‡āļŦāļĄāļ§āļ”āļŦāļĄāļđāđˆāļ—āļĩāđˆāđāļŠāļ”āļ‡: .including([.restaurant, .cafe]) āļŦāļĢāļ·āļ­ .excluding([.nightlife])

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 3: āļˆāļ°āđ€āļžāļīāđˆāļĄāđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāđāļĨāļ°āļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļ›āļĢāļ°āļāļ­āļšāļšāļ™āđāļœāļ™āļ—āļĩāđˆāđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāđāļ—āļ™āļˆāļļāļ”āļ—āļĩāđˆāļ™āđˆāļēāļŠāļ™āđƒāļˆāļšāļ™āđāļœāļ™āļ—āļĩāđˆ SwiftUI MapKit āļĄāļĩāļŦāļĨāļēāļĒāļ›āļĢāļ°āđ€āļ āļ— āđ„āļ”āđ‰āđāļāđˆ Marker (āļžāļīāļ™āļĄāļēāļ•āļĢāļāļēāļ™), Annotation (view āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡) āđāļĨāļ° MapCircle/MapPolygon (āļĢāļđāļ›āļ—āļĢāļ‡āđ€āļĢāļ‚āļēāļ„āļ“āļīāļ•)

MarkersAndAnnotations.swiftswift
import SwiftUI
import MapKit

// Place model conforming to Identifiable
struct Place: Identifiable {
    let id = UUID()
    let name: String
    let coordinate: CLLocationCoordinate2D
    let category: PlaceCategory
}

enum PlaceCategory {
    case restaurant, hotel, attraction

    var icon: String {
        switch self {
        case .restaurant: return "fork.knife"
        case .hotel: return "bed.double"
        case .attraction: return "star"
        }
    }

    var tint: Color {
        switch self {
        case .restaurant: return .orange
        case .hotel: return .blue
        case .attraction: return .purple
        }
    }
}

struct MarkersMapView: View {
    @State private var position: MapCameraPosition = .region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
            span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
        )
    )

    let places: [Place] = [
        Place(name: "Eiffel Tower", coordinate: CLLocationCoordinate2D(latitude: 48.8584, longitude: 2.2945), category: .attraction),
        Place(name: "Le Meurice", coordinate: CLLocationCoordinate2D(latitude: 48.8651, longitude: 2.3281), category: .hotel),
        Place(name: "Le Jules Verne", coordinate: CLLocationCoordinate2D(latitude: 48.8583, longitude: 2.2944), category: .restaurant)
    ]

    var body: some View {
        Map(position: $position) {
            // Markers with system icon
            ForEach(places) { place in
                Marker(place.name, systemImage: place.category.icon, coordinate: place.coordinate)
                    .tint(place.category.tint)
            }
        }
    }
}

// Custom annotations with SwiftUI view
struct CustomAnnotationsView: View {
    @State private var position: MapCameraPosition = .automatic
    @State private var selectedPlace: Place?

    let places: [Place] = [] // Data

    var body: some View {
        Map(position: $position, selection: $selectedPlace) {
            ForEach(places) { place in
                // Annotation with custom view
                Annotation(place.name, coordinate: place.coordinate) {
                    PlaceAnnotationView(place: place, isSelected: selectedPlace?.id == place.id)
                }
            }
        }
    }
}

// Custom view for annotation
struct PlaceAnnotationView: View {
    let place: Place
    let isSelected: Bool

    var body: some View {
        VStack(spacing: 4) {
            // Icon with colored background
            Image(systemName: place.category.icon)
                .font(.system(size: isSelected ? 20 : 16))
                .foregroundStyle(.white)
                .padding(8)
                .background(place.category.tint)
                .clipShape(Circle())
                .shadow(radius: 4)

            // Name displayed when selected
            if isSelected {
                Text(place.name)
                    .font(.caption)
                    .fontWeight(.medium)
                    .padding(.horizontal, 8)
                    .padding(.vertical, 4)
                    .background(.ultraThinMaterial)
                    .clipShape(RoundedRectangle(cornerRadius: 8))
            }
        }
        .animation(.spring(duration: 0.3), value: isSelected)
    }
}

āļ„āļ§āļēāļĄāđāļ•āļāļ•āđˆāļēāļ‡āļŦāļĨāļąāļ: Marker āđƒāļŠāđ‰āļāļēāļĢāđāļŠāļ”āļ‡āļœāļĨāđāļšāļšāđ€āļ™āļ—āļĩāļŸāļ‚āļ­āļ‡ Apple Maps (āļžāļīāļ™āļĄāļēāļ•āļĢāļāļēāļ™) āđƒāļ™āļ‚āļ“āļ°āļ—āļĩāđˆ Annotation āļ­āļ™āļļāļāļēāļ•āđƒāļŦāđ‰āļ›āļĢāļąāļšāđāļ•āđˆāļ‡āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļŠāļĄāļšāļđāļĢāļ“āđŒāļ”āđ‰āļ§āļĒ view āđƒāļ” āđ† āļ‚āļ­āļ‡ SwiftUI

āļāļēāļĢāļĢāļ°āļšāļļāļ•āļģāđāļŦāļ™āđˆāļ‡āđāļĨāļ°āļāļēāļĢāļ­āļ™āļļāļāļēāļ•

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 4: āļˆāļ°āļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļ­āļ™āļļāļāļēāļ•āļ•āļģāđāļŦāļ™āđˆāļ‡āđāļĨāļ°āđāļŠāļ”āļ‡āļ•āļģāđāļŦāļ™āđˆāļ‡āļ‚āļ­āļ‡āļœāļđāđ‰āđƒāļŠāđ‰āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļĢāļ°āļšāļļāļ•āļģāđāļŦāļ™āđˆāļ‡āļ•āđ‰āļ­āļ‡āļāļēāļĢāļāļēāļĢāļ­āļ™āļļāļāļēāļ•āļ­āļĒāđˆāļēāļ‡āļŠāļąāļ”āđ€āļˆāļ™āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļŠāļ–āļēāļ™āļ°āļāļēāļĢāļ­āļ™āļļāļāļēāļ•āļ•āđˆāļēāļ‡ āđ† āļ­āļĒāđˆāļēāļ‡āđ€āļŦāļĄāļēāļ°āļŠāļĄ CLLocationManager āļ”āļđāđāļĨāļāļēāļĢāļ‚āļ­āļ­āļ™āļļāļāļēāļ•

LocationManager.swiftswift
import SwiftUI
import MapKit
import CoreLocation

@Observable
class LocationManager: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()

    // Location state
    var location: CLLocation?
    var authorizationStatus: CLAuthorizationStatus = .notDetermined
    var isAuthorized: Bool {
        authorizationStatus == .authorizedWhenInUse || authorizationStatus == .authorizedAlways
    }

    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        // Get current status
        authorizationStatus = manager.authorizationStatus
    }

    // Request location permission
    func requestAuthorization() {
        manager.requestWhenInUseAuthorization()
    }

    // Start location tracking
    func startUpdatingLocation() {
        guard isAuthorized else { return }
        manager.startUpdatingLocation()
    }

    // Stop tracking to save battery
    func stopUpdatingLocation() {
        manager.stopUpdatingLocation()
    }

    // MARK: - CLLocationManagerDelegate

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        authorizationStatus = manager.authorizationStatus

        if isAuthorized {
            startUpdatingLocation()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // Take the most recent position
        location = locations.last
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Location error: \(error.localizedDescription)")
    }
}

// View with map and user location
struct UserLocationMapView: View {
    @State private var locationManager = LocationManager()
    @State private var cameraPosition: MapCameraPosition = .userLocation(fallback: .automatic)

    var body: some View {
        Map(position: $cameraPosition) {
            // Display the blue user location dot
            UserAnnotation()
        }
        .mapControls {
            // Button to recenter on position
            MapUserLocationButton()
            // Compass
            MapCompass()
            // Scale
            MapScaleView()
        }
        .onAppear {
            locationManager.requestAuthorization()
        }
        .overlay(alignment: .top) {
            if !locationManager.isAuthorized {
                PermissionBanner()
            }
        }
    }
}

// Banner requesting authorization
struct PermissionBanner: View {
    var body: some View {
        HStack {
            Image(systemName: "location.slash")
            Text("Enable location in Settings")
            Spacer()
            Button("Open") {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    UIApplication.shared.open(url)
                }
            }
            .buttonStyle(.bordered)
        }
        .padding()
        .background(.ultraThinMaterial)
    }
}
Info.plist āļ—āļĩāđˆāļˆāļģāđ€āļ›āđ‡āļ™

āļ„āļĩāļĒāđŒ NSLocationWhenInUseUsageDescription āđāļĨāļ°/āļŦāļĢāļ·āļ­ NSLocationAlwaysUsageDescription āļ•āđ‰āļ­āļ‡āļ–āļđāļāļāļģāļŦāļ™āļ”āđƒāļ™ Info.plist āļžāļĢāđ‰āļ­āļĄāļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™āđ€āļāļĩāđˆāļĒāļ§āļāļąāļšāļāļēāļĢāđƒāļŠāđ‰āļ•āļģāđāļŦāļ™āđˆāļ‡

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 5: āļˆāļ°āļ•āļīāļ”āļ•āļēāļĄāļ•āļģāđāļŦāļ™āđˆāļ‡āđāļšāļšāđ€āļĢāļĩāļĒāļĨāđ„āļ—āļĄāđŒāđāļĨāļ°āļ§āļēāļ”āđ€āļŠāđ‰āļ™āļ—āļēāļ‡āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļ•āļīāļ”āļ•āļēāļĄāđāļšāļšāđ€āļĢāļĩāļĒāļĨāđ„āļ—āļĄāđŒāđ€āļ›āđ‡āļ™āļŠāļīāđˆāļ‡āļˆāļģāđ€āļ›āđ‡āļ™āļŠāļģāļŦāļĢāļąāļšāđāļ­āļ›āļŸāļīāļ•āđ€āļ™āļŠāļŦāļĢāļ·āļ­āļāļēāļĢāļ™āļģāļ—āļēāļ‡ āļ„āļģāļ–āļēāļĄāļ™āļĩāđ‰āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļ–āđƒāļ™āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļāļĢāļ°āđāļŠāļ‚āđ‰āļ­āļĄāļđāļĨāļ•āļģāđāļŦāļ™āđˆāļ‡āļ­āļĒāđˆāļēāļ‡āļ•āđˆāļ­āđ€āļ™āļ·āđˆāļ­āļ‡

RouteTrackingView.swiftswift
import SwiftUI
import MapKit
import CoreLocation

@Observable
class RouteTracker: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()

    // Position history for the trace
    var routeCoordinates: [CLLocationCoordinate2D] = []
    var currentLocation: CLLocation?
    var isTracking = false

    // Route statistics
    var totalDistance: CLLocationDistance = 0
    private var lastLocation: CLLocation?

    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        // Update even in background
        manager.allowsBackgroundLocationUpdates = true
        manager.pausesLocationUpdatesAutomatically = false
    }

    func startTracking() {
        manager.requestWhenInUseAuthorization()
        manager.startUpdatingLocation()
        isTracking = true
        routeCoordinates = []
        totalDistance = 0
        lastLocation = nil
    }

    func stopTracking() {
        manager.stopUpdatingLocation()
        isTracking = false
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let newLocation = locations.last else { return }

        // Filter inaccurate positions
        guard newLocation.horizontalAccuracy < 20 else { return }

        currentLocation = newLocation
        routeCoordinates.append(newLocation.coordinate)

        // Calculate distance traveled
        if let last = lastLocation {
            totalDistance += newLocation.distance(from: last)
        }
        lastLocation = newLocation
    }
}

struct RouteTrackingView: View {
    @State private var tracker = RouteTracker()
    @State private var cameraPosition: MapCameraPosition = .userLocation(fallback: .automatic)

    var body: some View {
        ZStack {
            Map(position: $cameraPosition) {
                // Current position
                UserAnnotation()

                // Route trace as polyline
                if tracker.routeCoordinates.count > 1 {
                    MapPolyline(coordinates: tracker.routeCoordinates)
                        .stroke(.blue, lineWidth: 4)
                }
            }
            .mapStyle(.standard)

            // Control interface
            VStack {
                Spacer()

                // Statistics
                HStack {
                    VStack(alignment: .leading) {
                        Text("Distance")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                        Text(formatDistance(tracker.totalDistance))
                            .font(.title2)
                            .fontWeight(.bold)
                    }
                    Spacer()
                }
                .padding()
                .background(.ultraThinMaterial)
                .clipShape(RoundedRectangle(cornerRadius: 12))
                .padding()

                // Start/stop button
                Button {
                    if tracker.isTracking {
                        tracker.stopTracking()
                    } else {
                        tracker.startTracking()
                    }
                } label: {
                    Label(
                        tracker.isTracking ? "Stop" : "Start",
                        systemImage: tracker.isTracking ? "stop.fill" : "play.fill"
                    )
                    .frame(maxWidth: .infinity)
                }
                .buttonStyle(.borderedProminent)
                .tint(tracker.isTracking ? .red : .green)
                .padding(.horizontal)
                .padding(.bottom)
            }
        }
    }

    private func formatDistance(_ meters: CLLocationDistance) -> String {
        if meters < 1000 {
            return String(format: "%.0f m", meters)
        } else {
            return String(format: "%.2f km", meters / 1000)
        }
    }
}

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļāļēāļĢāļ‹āđ‰āļ­āļ™āļ—āļąāļšāđāļĨāļ°āļĢāļđāļ›āļ—āļĢāļ‡āđ€āļĢāļ‚āļēāļ„āļ“āļīāļ•

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 6: āļˆāļ°āļ§āļēāļ”āļāļēāļĢāļ‹āđ‰āļ­āļ™āļ—āļąāļšāđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡āļšāļ™āđāļœāļ™āļ—āļĩāđˆāđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļ‹āđ‰āļ­āļ™āļ—āļąāļšāļŠāđˆāļ§āļĒāđƒāļŦāđ‰āđāļŠāļ”āļ‡āđ‚āļ‹āļ™ āđ€āļŠāđ‰āļ™āļ—āļēāļ‡ āļŦāļĢāļ·āļ­āļžāļ·āđ‰āļ™āļ—āļĩāđˆāļšāļ™āđāļœāļ™āļ—āļĩāđˆāđ„āļ”āđ‰ SwiftUI MapKit āļĄāļĩ MapCircle, MapPolygon āđāļĨāļ° MapPolyline

MapOverlays.swiftswift
import SwiftUI
import MapKit

struct DeliveryZoneMapView: View {
    @State private var position: MapCameraPosition = .region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
            span: MKCoordinateSpan(latitudeDelta: 0.15, longitudeDelta: 0.15)
        )
    )

    // Delivery zones with different times
    let deliveryZones: [DeliveryZone] = [
        DeliveryZone(
            name: "Express Zone",
            center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
            radius: 2000,
            deliveryTime: "15 min",
            color: .green
        ),
        DeliveryZone(
            name: "Standard Zone",
            center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
            radius: 5000,
            deliveryTime: "30 min",
            color: .orange
        )
    ]

    var body: some View {
        Map(position: $position) {
            // Circles for delivery zones
            ForEach(deliveryZones) { zone in
                MapCircle(center: zone.center, radius: zone.radius)
                    .foregroundStyle(zone.color.opacity(0.2))
                    .stroke(zone.color, lineWidth: 2)
            }

            // Restaurant marker
            Marker("Restaurant", systemImage: "storefront", coordinate: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522))
                .tint(.red)
        }
        .overlay(alignment: .bottomLeading) {
            // Legend
            VStack(alignment: .leading, spacing: 8) {
                ForEach(deliveryZones) { zone in
                    HStack {
                        Circle()
                            .fill(zone.color)
                            .frame(width: 12, height: 12)
                        Text("\(zone.name) - \(zone.deliveryTime)")
                            .font(.caption)
                    }
                }
            }
            .padding()
            .background(.ultraThinMaterial)
            .clipShape(RoundedRectangle(cornerRadius: 8))
            .padding()
        }
    }
}

struct DeliveryZone: Identifiable {
    let id = UUID()
    let name: String
    let center: CLLocationCoordinate2D
    let radius: CLLocationDistance
    let deliveryTime: String
    let color: Color
}

// Custom polygon (complex geographic zone)
struct PolygonOverlayView: View {
    @State private var position: MapCameraPosition = .automatic

    // Neighborhood coordinates
    let neighborhoodCoordinates: [CLLocationCoordinate2D] = [
        CLLocationCoordinate2D(latitude: 48.853, longitude: 2.347),
        CLLocationCoordinate2D(latitude: 48.858, longitude: 2.352),
        CLLocationCoordinate2D(latitude: 48.862, longitude: 2.348),
        CLLocationCoordinate2D(latitude: 48.860, longitude: 2.340),
        CLLocationCoordinate2D(latitude: 48.855, longitude: 2.342)
    ]

    var body: some View {
        Map(position: $position) {
            // Polygon automatically closed
            MapPolygon(coordinates: neighborhoodCoordinates)
                .foregroundStyle(.blue.opacity(0.3))
                .stroke(.blue, lineWidth: 3)
        }
    }
}

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 7: āļˆāļ°āđāļŠāļ”āļ‡āđ€āļŠāđ‰āļ™āļ—āļēāļ‡āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļŠāļ­āļ‡āļˆāļļāļ”āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāļ„āļģāļ™āļ§āļ“āđ€āļŠāđ‰āļ™āļ—āļēāļ‡āđƒāļŠāđ‰ MKDirections āđ€āļžāļ·āđˆāļ­āļĢāļąāļšāđ€āļŠāđ‰āļ™āļ—āļēāļ‡āļˆāļēāļāđ€āļ‹āļīāļĢāđŒāļŸāđ€āļ§āļ­āļĢāđŒāļ‚āļ­āļ‡ Apple āļˆāļēāļāļ™āļąāđ‰āļ™āļˆāļ°āđāļŠāļ”āļ‡āļœāļĨāļ”āđ‰āļ§āļĒ MapPolyline

RouteDirections.swiftswift
import SwiftUI
import MapKit

@Observable
class RouteCalculator {
    var route: MKRoute?
    var isCalculating = false
    var error: String?

    // Calculate route between two points
    func calculateRoute(from source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) async {
        isCalculating = true
        error = nil

        // Create source and destination placemarks
        let sourcePlacemark = MKPlacemark(coordinate: source)
        let destinationPlacemark = MKPlacemark(coordinate: destination)

        // Configure request
        let request = MKDirections.Request()
        request.source = MKMapItem(placemark: sourcePlacemark)
        request.destination = MKMapItem(placemark: destinationPlacemark)
        request.transportType = .automobile // .walking, .transit available
        request.requestsAlternateRoutes = false

        let directions = MKDirections(request: request)

        do {
            let response = try await directions.calculate()
            // Take the first route
            route = response.routes.first
        } catch {
            self.error = error.localizedDescription
        }

        isCalculating = false
    }
}

struct DirectionsMapView: View {
    @State private var calculator = RouteCalculator()
    @State private var position: MapCameraPosition = .automatic

    let startPoint = CLLocationCoordinate2D(latitude: 48.8738, longitude: 2.2950) // Arc de Triomphe
    let endPoint = CLLocationCoordinate2D(latitude: 48.8530, longitude: 2.3499) // Notre-Dame

    var body: some View {
        ZStack {
            Map(position: $position) {
                // Start and end markers
                Marker("Start", systemImage: "car.fill", coordinate: startPoint)
                    .tint(.green)

                Marker("End", systemImage: "flag.fill", coordinate: endPoint)
                    .tint(.red)

                // Route trace
                if let route = calculator.route {
                    MapPolyline(route.polyline)
                        .stroke(.blue, lineWidth: 5)
                }
            }

            // Route information
            if let route = calculator.route {
                VStack {
                    Spacer()
                    RouteInfoCard(route: route)
                }
            }

            if calculator.isCalculating {
                ProgressView("Calculating route...")
                    .padding()
                    .background(.ultraThinMaterial)
                    .clipShape(RoundedRectangle(cornerRadius: 8))
            }
        }
        .task {
            await calculator.calculateRoute(from: startPoint, to: endPoint)
        }
    }
}

struct RouteInfoCard: View {
    let route: MKRoute

    var body: some View {
        HStack(spacing: 20) {
            VStack(alignment: .leading) {
                Text("Distance")
                    .font(.caption)
                    .foregroundStyle(.secondary)
                Text(formatDistance(route.distance))
                    .font(.headline)
            }

            Divider()
                .frame(height: 30)

            VStack(alignment: .leading) {
                Text("Estimated Time")
                    .font(.caption)
                    .foregroundStyle(.secondary)
                Text(formatDuration(route.expectedTravelTime))
                    .font(.headline)
            }

            Spacer()
        }
        .padding()
        .background(.ultraThinMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 12))
        .padding()
    }

    private func formatDistance(_ meters: CLLocationDistance) -> String {
        let km = meters / 1000
        return String(format: "%.1f km", km)
    }

    private func formatDuration(_ seconds: TimeInterval) -> String {
        let minutes = Int(seconds) / 60
        if minutes < 60 {
            return "\(minutes) min"
        } else {
            let hours = minutes / 60
            let remainingMinutes = minutes % 60
            return "\(hours)h \(remainingMinutes)min"
        }
    }
}

āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļŠāļ–āļēāļ™āļ—āļĩāđˆāđāļĨāļ°āļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒ

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 8: āļˆāļ°āđƒāļŠāđ‰āļ‡āļēāļ™āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļŠāļ–āļēāļ™āļ—āļĩāđˆāļ”āđ‰āļ§āļĒ MKLocalSearch āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

MKLocalSearch āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ„āđ‰āļ™āļŦāļēāļˆāļļāļ”āļ—āļĩāđˆāļ™āđˆāļēāļŠāļ™āđƒāļˆ āļ—āļĩāđˆāļ­āļĒāļđāđˆ āļŦāļĢāļ·āļ­āļ˜āļļāļĢāļāļīāļˆāđ„āļ”āđ‰ āļ„āļļāļ“āļŠāļĄāļšāļąāļ•āļīāļ™āļĩāđ‰āđ€āļ›āđ‡āļ™āļŠāļīāđˆāļ‡āļˆāļģāđ€āļ›āđ‡āļ™āļŠāļģāļŦāļĢāļąāļšāđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āļ—āļĩāđˆāļĄāļĩāđāļ–āļšāļ„āđ‰āļ™āļŦāļēāļ•āļēāļĄāļ•āļģāđāļŦāļ™āđˆāļ‡

PlaceSearch.swiftswift
import SwiftUI
import MapKit

@Observable
class PlaceSearchManager {
    var searchResults: [MKMapItem] = []
    var isSearching = false
    var searchText = ""

    private var searchTask: Task<Void, Never>?

    // Search with debounce
    func search(query: String, in region: MKCoordinateRegion) {
        searchTask?.cancel()

        guard !query.isEmpty else {
            searchResults = []
            return
        }

        searchTask = Task {
            // 300ms debounce
            try? await Task.sleep(for: .milliseconds(300))

            guard !Task.isCancelled else { return }

            isSearching = true

            let request = MKLocalSearch.Request()
            request.naturalLanguageQuery = query
            request.region = region
            request.resultTypes = [.pointOfInterest, .address]

            let search = MKLocalSearch(request: request)

            do {
                let response = try await search.start()
                if !Task.isCancelled {
                    searchResults = response.mapItems
                }
            } catch {
                print("Search error: \(error.localizedDescription)")
                searchResults = []
            }

            isSearching = false
        }
    }

    func clearResults() {
        searchResults = []
        searchText = ""
    }
}

struct SearchableMapView: View {
    @State private var searchManager = PlaceSearchManager()
    @State private var position: MapCameraPosition = .region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 48.8566, longitude: 2.3522),
            span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
        )
    )
    @State private var selectedItem: MKMapItem?
    @State private var visibleRegion: MKCoordinateRegion?

    var body: some View {
        NavigationStack {
            Map(position: $position, selection: $selectedItem) {
                // Display search results
                ForEach(searchManager.searchResults, id: \.self) { item in
                    Marker(item: item)
                }
            }
            .onMapCameraChange { context in
                // Capture visible region for search
                visibleRegion = context.region
            }
            .searchable(text: $searchManager.searchText, prompt: "Search for a place")
            .onChange(of: searchManager.searchText) { _, newValue in
                if let region = visibleRegion {
                    searchManager.search(query: newValue, in: region)
                }
            }
            .overlay(alignment: .bottom) {
                if let item = selectedItem {
                    PlaceDetailCard(item: item)
                }
            }
            .navigationTitle("Map")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct PlaceDetailCard: View {
    let item: MKMapItem

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(item.name ?? "Unknown Place")
                .font(.headline)

            if let address = item.placemark.title {
                Text(address)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            HStack {
                if let phone = item.phoneNumber {
                    Button {
                        if let url = URL(string: "tel:\(phone)") {
                            UIApplication.shared.open(url)
                        }
                    } label: {
                        Label("Call", systemImage: "phone")
                    }
                    .buttonStyle(.bordered)
                }

                Button {
                    item.openInMaps()
                } label: {
                    Label("Directions", systemImage: "arrow.triangle.turn.up.right.diamond")
                }
                .buttonStyle(.borderedProminent)
            }
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(.ultraThickMaterial)
        .clipShape(RoundedRectangle(cornerRadius: 16))
        .padding()
    }
}

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 9: āļˆāļ°āđāļ›āļĨāļ‡āļ—āļĩāđˆāļ­āļĒāļđāđˆāđ€āļ›āđ‡āļ™āļžāļīāļāļąāļ”āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ (āļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒ)?

āļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒāđāļ›āļĨāļ‡āļ—āļĩāđˆāļ­āļĒāļđāđˆāļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļ‚āđ‰āļ­āļ„āļ§āļēāļĄāđ€āļ›āđ‡āļ™āļžāļīāļāļąāļ” GPS āļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒāđāļšāļšāļĒāđ‰āļ­āļ™āļāļĨāļąāļšāļ—āļģāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđƒāļ™āļ—āļīāļĻāļ—āļēāļ‡āļ•āļĢāļ‡āļāļąāļ™āļ‚āđ‰āļēāļĄ

Geocoding.swiftswift
import SwiftUI
import MapKit
import CoreLocation

@Observable
class GeocodingManager {
    private let geocoder = CLGeocoder()

    var coordinate: CLLocationCoordinate2D?
    var address: String?
    var isLoading = false
    var error: String?

    // Geocoding: address → coordinates
    func geocode(address: String) async {
        isLoading = true
        error = nil

        do {
            let placemarks = try await geocoder.geocodeAddressString(address)

            if let placemark = placemarks.first,
               let location = placemark.location {
                coordinate = location.coordinate
            } else {
                error = "Address not found"
            }
        } catch {
            self.error = error.localizedDescription
        }

        isLoading = false
    }

    // Reverse geocoding: coordinates → address
    func reverseGeocode(coordinate: CLLocationCoordinate2D) async {
        isLoading = true
        error = nil

        let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)

        do {
            let placemarks = try await geocoder.reverseGeocodeLocation(location)

            if let placemark = placemarks.first {
                address = formatAddress(placemark)
            } else {
                error = "Address not found"
            }
        } catch {
            self.error = error.localizedDescription
        }

        isLoading = false
    }

    // Format readable address
    private func formatAddress(_ placemark: CLPlacemark) -> String {
        var components: [String] = []

        if let street = placemark.thoroughfare {
            if let number = placemark.subThoroughfare {
                components.append("\(number) \(street)")
            } else {
                components.append(street)
            }
        }

        if let city = placemark.locality {
            components.append(city)
        }

        if let country = placemark.country {
            components.append(country)
        }

        return components.joined(separator: ", ")
    }
}

struct GeocodingDemoView: View {
    @State private var manager = GeocodingManager()
    @State private var addressInput = ""
    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        VStack {
            // Address input field
            HStack {
                TextField("Enter an address", text: $addressInput)
                    .textFieldStyle(.roundedBorder)

                Button("Search") {
                    Task {
                        await manager.geocode(address: addressInput)
                        if let coord = manager.coordinate {
                            position = .region(MKCoordinateRegion(
                                center: coord,
                                span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
                            ))
                        }
                    }
                }
                .buttonStyle(.borderedProminent)
                .disabled(addressInput.isEmpty || manager.isLoading)
            }
            .padding()

            // Map with marker
            Map(position: $position) {
                if let coord = manager.coordinate {
                    Marker("Result", coordinate: coord)
                        .tint(.red)
                }
            }

            if let error = manager.error {
                Text(error)
                    .foregroundStyle(.red)
                    .padding()
            }
        }
    }
}
āļ‚āđ‰āļ­āļˆāļģāļāļąāļ”āļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒ

āļ•āļąāļ§āđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒāļ‚āļ­āļ‡ Apple āļĄāļĩāļ‚āđ‰āļ­āļˆāļģāļāļąāļ”āđƒāļ™āļāļēāļĢāļĢāđ‰āļ­āļ‡āļ‚āļ­ āļŠāļģāļŦāļĢāļąāļšāļ›āļĢāļīāļĄāļēāļ“āļ—āļĩāđˆāļŠāļđāļ‡ āđāļ™āļ°āļ™āļģāđƒāļŦāđ‰āđƒāļŠāđ‰āđāļ„āļŠāđƒāļ™āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĢāļ·āļ­āļšāļĢāļīāļāļēāļĢāļ‚āļ­āļ‡āļšāļļāļ„āļ„āļĨāļ—āļĩāđˆāļŠāļēāļĄ āđ€āļŠāđˆāļ™ Mapbox āļŦāļĢāļ·āļ­ Google Maps

āļĢāļđāļ›āđāļšāļšāļ‚āļąāđ‰āļ™āļŠāļđāļ‡āđāļĨāļ°āđāļ™āļ§āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩāļ—āļĩāđˆāļŠāļļāļ”

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 10: āļˆāļ°āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāđ€āļĄāļ·āđˆāļ­āļĄāļĩāđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāļˆāļģāļ™āļ§āļ™āļĄāļēāļāđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

āļāļēāļĢāđāļŠāļ”āļ‡āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒāļŦāļĨāļēāļĒāļĢāđ‰āļ­āļĒāļ­āļąāļ™āļ­āļēāļˆāļŠāđˆāļ‡āļœāļĨāļāļĢāļ°āļ—āļšāļ•āđˆāļ­āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž āļāļēāļĢāļˆāļąāļ”āļāļĨāļļāđˆāļĄāđāļĨāļ°āļāļēāļĢāđ‚āļŦāļĨāļ”āļ—āļĩāļĨāļ°āļ™āđ‰āļ­āļĒāđ€āļ›āđ‡āļ™āļ§āļīāļ˜āļĩāđāļāđ‰āļ›āļąāļāļŦāļēāļ—āļĩāđˆāļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž

ClusteringMapView.swiftswift
import SwiftUI
import MapKit

struct ClusteringMapView: View {
    @State private var position: MapCameraPosition = .automatic
    @State private var visiblePlaces: [Place] = []
    @State private var visibleRegion: MKCoordinateRegion?

    // All data (potentially thousands)
    let allPlaces: [Place]

    var body: some View {
        Map(position: $position) {
            // Display only visible places
            ForEach(visiblePlaces) { place in
                Marker(place.name, coordinate: place.coordinate)
            }
        }
        .onMapCameraChange(frequency: .onEnd) { context in
            // Update when user stops moving
            visibleRegion = context.region
            updateVisiblePlaces(in: context.region)
        }
    }

    // Filter places in visible region
    private func updateVisiblePlaces(in region: MKCoordinateRegion) {
        let minLat = region.center.latitude - region.span.latitudeDelta / 2
        let maxLat = region.center.latitude + region.span.latitudeDelta / 2
        let minLon = region.center.longitude - region.span.longitudeDelta / 2
        let maxLon = region.center.longitude + region.span.longitudeDelta / 2

        // Spatial filtering
        var filtered = allPlaces.filter { place in
            place.coordinate.latitude >= minLat &&
            place.coordinate.latitude <= maxLat &&
            place.coordinate.longitude >= minLon &&
            place.coordinate.longitude <= maxLon
        }

        // Limit number of displayed markers
        if filtered.count > 100 {
            // Sampling or clustering
            filtered = Array(filtered.prefix(100))
        }

        visiblePlaces = filtered
    }
}

// Simplified manual clustering
struct ClusteredPlace: Identifiable {
    let id = UUID()
    let coordinate: CLLocationCoordinate2D
    let count: Int
    let places: [Place]

    var isCluster: Bool { count > 1 }
}

@Observable
class ClusterManager {
    var clusters: [ClusteredPlace] = []

    // Group nearby places based on zoom level
    func cluster(places: [Place], in region: MKCoordinateRegion) {
        let gridSize = region.span.latitudeDelta / 10

        var grid: [String: [Place]] = [:]

        for place in places {
            // Grid key based on position
            let gridX = Int(place.coordinate.longitude / gridSize)
            let gridY = Int(place.coordinate.latitude / gridSize)
            let key = "\(gridX),\(gridY)"

            grid[key, default: []].append(place)
        }

        // Convert to clusters
        clusters = grid.map { (_, places) in
            let centerLat = places.map(\.coordinate.latitude).reduce(0, +) / Double(places.count)
            let centerLon = places.map(\.coordinate.longitude).reduce(0, +) / Double(places.count)

            return ClusteredPlace(
                coordinate: CLLocationCoordinate2D(latitude: centerLat, longitude: centerLon),
                count: places.count,
                places: places
            )
        }
    }
}

struct ClusterAnnotationView: View {
    let cluster: ClusteredPlace

    var body: some View {
        if cluster.isCluster {
            // Display a circle with count
            ZStack {
                Circle()
                    .fill(.blue)
                    .frame(width: 40, height: 40)

                Text("\(cluster.count)")
                    .font(.system(size: 14, weight: .bold))
                    .foregroundStyle(.white)
            }
        } else {
            // Simple marker
            Image(systemName: "mappin.circle.fill")
                .font(.title)
                .foregroundStyle(.red)
        }
    }
}

āļ„āļģāļ–āļēāļĄāļ—āļĩāđˆ 11: āļˆāļ°āļœāļŠāļēāļ™āļĢāļ§āļĄ MapKit āļāļąāļšāļšāļĢāļīāļāļēāļĢāļ­āļ·āđˆāļ™ āđ† āļ‚āļ­āļ‡ Apple āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢ?

MapKit āļœāļŠāļēāļ™āļĢāļ§āļĄāļāļąāļšāđ€āļŸāļĢāļĄāđ€āļ§āļīāļĢāđŒāļāļ‚āļ­āļ‡ Apple āļŦāļĨāļēāļĒāļ•āļąāļ§āđ€āļžāļ·āđˆāļ­āļĄāļ­āļšāļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļ—āļĩāđˆāļ”āļĩāļ‚āļķāđ‰āļ™ āđ„āļ”āđ‰āđāļāđˆ Look Around, CarPlay, WidgetKit

LookAroundIntegration.swiftswift
import SwiftUI
import MapKit

struct LookAroundMapView: View {
    @State private var position: MapCameraPosition = .automatic
    @State private var selectedPlace: Place?
    @State private var lookAroundScene: MKLookAroundScene?
    @State private var showLookAround = false

    let places: [Place]

    var body: some View {
        Map(position: $position, selection: $selectedPlace) {
            ForEach(places) { place in
                Marker(place.name, coordinate: place.coordinate)
            }
        }
        .onChange(of: selectedPlace) { _, newPlace in
            if let place = newPlace {
                Task {
                    await loadLookAroundScene(for: place.coordinate)
                }
            }
        }
        .sheet(isPresented: $showLookAround) {
            if let scene = lookAroundScene {
                LookAroundPreview(scene: scene)
                    .frame(height: 300)
            }
        }
        .safeAreaInset(edge: .bottom) {
            if selectedPlace != nil && lookAroundScene != nil {
                Button("View in Look Around") {
                    showLookAround = true
                }
                .buttonStyle(.borderedProminent)
                .padding()
            }
        }
    }

    // Load Look Around scene for a position
    private func loadLookAroundScene(for coordinate: CLLocationCoordinate2D) async {
        let request = MKLookAroundSceneRequest(coordinate: coordinate)

        do {
            lookAroundScene = try await request.scene
        } catch {
            lookAroundScene = nil
            print("Look Around not available: \(error.localizedDescription)")
        }
    }
}

// Interactive Look Around component
struct LookAroundPreview: View {
    let scene: MKLookAroundScene
    @State private var isNavigating = false

    var body: some View {
        LookAroundPreviewRepresentable(scene: scene, isNavigating: $isNavigating)
            .overlay(alignment: .topTrailing) {
                Button {
                    isNavigating.toggle()
                } label: {
                    Image(systemName: isNavigating ? "stop.fill" : "play.fill")
                        .padding()
                        .background(.ultraThinMaterial)
                        .clipShape(Circle())
                }
                .padding()
            }
    }
}

// UIViewRepresentable for Look Around
struct LookAroundPreviewRepresentable: UIViewRepresentable {
    let scene: MKLookAroundScene
    @Binding var isNavigating: Bool

    func makeUIView(context: Context) -> MKLookAroundViewController {
        let controller = MKLookAroundViewController(scene: scene)
        controller.isNavigationEnabled = isNavigating
        return controller
    }

    func updateUIView(_ uiView: MKLookAroundViewController, context: Context) {
        uiView.isNavigationEnabled = isNavigating
    }
}

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļšāļ—āļŠāļĢāļļāļ›

MapKit āļāļąāļš SwiftUI āļĄāļ­āļš API āļ—āļĩāđˆāļ—āļąāļ™āļŠāļĄāļąāļĒāđāļĨāļ°āļ—āļĢāļ‡āļžāļĨāļąāļ‡āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļœāļŠāļēāļ™āļĢāļ§āļĄāđāļœāļ™āļ—āļĩāđˆāđƒāļ™āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™ iOS āļ„āļ§āļēāļĄāđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļāđƒāļ™āļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļ›āļĢāļ°āļāļ­āļš āļāļēāļĢāļ‹āđ‰āļ­āļ™āļ—āļąāļš āļāļēāļĢāļĢāļ°āļšāļļāļ•āļģāđāļŦāļ™āđˆāļ‡ āđāļĨāļ°āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļŠāļ–āļēāļ™āļ—āļĩāđˆāļ—āļģāđƒāļŦāđ‰āļ™āļąāļāļžāļąāļ’āļ™āļē iOS āļ—āļĩāđˆāļĄāļĩāļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāđ‚āļ”āļ”āđ€āļ”āđˆāļ™āđƒāļ™āļĢāļ°āļŦāļ§āđˆāļēāļ‡āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„

āļĢāļēāļĒāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš

  • ✅ āđāļŠāļ”āļ‡āđāļœāļ™āļ—āļĩāđˆāļ”āđ‰āļ§āļĒ Map āđāļĨāļ°āļāļģāļŦāļ™āļ”āļ„āđˆāļē MapCameraPosition
  • ✅ āđƒāļŠāđ‰āļĢāļđāļ›āđāļšāļšāđāļœāļ™āļ—āļĩāđˆāļ•āđˆāļēāļ‡ āđ† (standard, imagery, hybrid)
  • ✅ āđ€āļžāļīāđˆāļĄ Marker āđāļĨāļ° Annotation āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡
  • ✅ āļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļ­āļ™āļļāļāļēāļ•āļ•āļģāđāļŦāļ™āđˆāļ‡āļ”āđ‰āļ§āļĒ CLLocationManager
  • ✅ āļ•āļīāļ”āļ•āļēāļĄāļ•āļģāđāļŦāļ™āđˆāļ‡āđāļšāļšāđ€āļĢāļĩāļĒāļĨāđ„āļ—āļĄāđŒāđāļĨāļ°āļ§āļēāļ”āđ€āļŠāđ‰āļ™āļ—āļēāļ‡
  • ✅ āļ§āļēāļ”āļāļēāļĢāļ‹āđ‰āļ­āļ™āļ—āļąāļš (MapCircle, MapPolygon, MapPolyline)
  • ✅ āļ„āļģāļ™āļ§āļ“āđ€āļŠāđ‰āļ™āļ—āļēāļ‡āļ”āđ‰āļ§āļĒ MKDirections
  • ✅ āđƒāļŠāđ‰āļ‡āļēāļ™āļāļēāļĢāļ„āđ‰āļ™āļŦāļēāļŠāļ–āļēāļ™āļ—āļĩāđˆāļ”āđ‰āļ§āļĒ MKLocalSearch
  • ✅ āļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒāđāļĨāļ°āđ€āļ‚āđ‰āļēāļĢāļŦāļąāļŠāļ āļđāļĄāļīāļĻāļēāļŠāļ•āļĢāđŒāđāļšāļšāļĒāđ‰āļ­āļ™āļāļĨāļąāļš
  • ✅ āļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļ”āđ‰āļ§āļĒāļāļēāļĢāļˆāļąāļ”āļāļĨāļļāđˆāļĄāđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļŦāļĄāļēāļĒ
  • ✅ āļœāļŠāļēāļ™āļĢāļ§āļĄ Look Around āđ€āļžāļ·āđˆāļ­āļĄāļļāļĄāļĄāļ­āļ‡āđāļšāļšāļ”āļ·āđˆāļĄāļ”āđˆāļģ

āđ€āļĢāļīāđˆāļĄāļāļķāļāļ‹āđ‰āļ­āļĄāđ€āļĨāļĒ!

āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļĢāļđāđ‰āļ‚āļ­āļ‡āļ„āļļāļ“āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āđāļ—āđ‡āļ

#mapkit
#swiftui
#ios
#geolocation
#interview

āđāļŠāļĢāđŒ

āļšāļ—āļ„āļ§āļēāļĄāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ SwiftUI NavigationStack āļŠāļģāļŦāļĢāļąāļšāļ™āļąāļāļžāļąāļ’āļ™āļē iOS

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ SwiftUI NavigationStack: āļĢāļđāļ›āđāļšāļšāļāļēāļĢāļ™āļģāļ—āļēāļ‡ 2026

āđ€āļ•āļĢāļĩāļĒāļĄāļ•āļąāļ§āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļ”āđ‰āļ§āļĒāļ„āļģāļ–āļēāļĄāļŠāļģāļ„āļąāļāđ€āļāļĩāđˆāļĒāļ§āļāļąāļš NavigationStack, NavigationPath āđāļĨāļ°āļĢāļđāļ›āđāļšāļšāļāļēāļĢāļ™āļģāļ—āļēāļ‡āļŠāļĄāļąāļĒāđƒāļŦāļĄāđˆāđƒāļ™ SwiftUI

āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ iOS StoreKit 2 āđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ StoreKit 2: āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļāđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ

āđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļāļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđ€āļāļĩāđˆāļĒāļ§āļāļąāļš StoreKit 2 āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļ„āļĢāļŠāļĄāļēāļŠāļīāļ āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđƒāļšāđ€āļŠāļĢāđ‡āļˆ āđāļĨāļ°āļāļēāļĢāļ™āļģāļāļēāļĢāļ‹āļ·āđ‰āļ­āđƒāļ™āđāļ­āļ›āđ„āļ›āđƒāļŠāđ‰ āļžāļĢāđ‰āļ­āļĄāļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ‚āļ„āđ‰āļ” Swift āļ—āļĩāđˆāđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰āļˆāļĢāļīāļ‡

Vision Framework āđāļĨāļ° CoreML āļŠāļģāļŦāļĢāļąāļš machine learning āļšāļ™āļ­āļļāļ›āļāļĢāļ“āđŒ iOS

Vision Framework āđāļĨāļ° CoreML: āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđ€āļĢāļ·āđˆāļ­āļ‡ ML āļšāļ™āļ­āļļāļ›āļāļĢāļ“āđŒ

āđ€āļ•āļĢāļĩāļĒāļĄāļ•āļąāļ§āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļ”āđ‰āļ§āļĒāļ„āļģāļ–āļēāļĄāļŠāļģāļ„āļąāļāđ€āļāļĩāđˆāļĒāļ§āļāļąāļš Vision Framework āđāļĨāļ° CoreML: āļāļēāļĢāļĢāļđāđ‰āļˆāļģāļ āļēāļž āļāļēāļĢāļ•āļĢāļ§āļˆāļˆāļąāļšāļ§āļąāļ•āļ–āļļ āđāļĨāļ° ML āļšāļ™āļ­āļļāļ›āļāļĢāļ“āđŒ