2026๋…„ MapKit SwiftUI ๋ฉด์ ‘: ์–ด๋…ธํ…Œ์ด์…˜, ์˜ค๋ฒ„๋ ˆ์ด, ์ง€์˜ค๋กœ์ผ€์ด์…˜

iOS ๋ฉด์ ‘์„ ์œ„ํ•œ SwiftUI์™€ MapKit ๋งˆ์Šคํ„ฐํ•˜๊ธฐ: ์‚ฌ์šฉ์ž ์ •์˜ ์–ด๋…ธํ…Œ์ด์…˜, ์˜ค๋ฒ„๋ ˆ์ด, ์ง€์˜ค๋กœ์ผ€์ด์…˜, ์žฅ์†Œ ๊ฒ€์ƒ‰, Maps ํ†ตํ•ฉ ํŒจํ„ด.

iOS ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ MapKit SwiftUI ๋ฉด์ ‘ ์งˆ๋ฌธ

์ง€๋„๋Š” ๋งŽ์€ iOS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค: ๋ฐฐ๋‹ฌ, ํ”ผํŠธ๋‹ˆ์Šค, ๋ถ€๋™์‚ฐ, ์œ„์น˜ ๊ธฐ๋ฐ˜ ์†Œ์…œ ๋„คํŠธ์›Œํฌ ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. iOS 17๋ถ€ํ„ฐ MapKit์€ ์ง€๋„ ํ†ตํ•ฉ์„ ํฌ๊ฒŒ ๋‹จ์ˆœํ™”ํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ SwiftUI API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ฑ„์šฉ ๋‹ด๋‹น์ž๋Š” ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ์ด๋Ÿฌํ•œ ๊ธฐ์ˆ ์„ ์ •๊ธฐ์ ์œผ๋กœ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€์ด๋“œ ๊ตฌ์กฐ

๊ฐ ์งˆ๋ฌธ์€ ์‹ค์ œ ๊ธฐ์ˆ  ๋ฉด์ ‘ ํ˜•์‹์„ ๋ฐ˜์˜ํ•˜๋ฉฐ, ์ž์„ธํ•œ ๋‹ต๋ณ€๊ณผ ์ž‘๋™ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋…์€ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ๊ณ ๊ธ‰๊นŒ์ง€ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

SwiftUI์—์„œ์˜ ์ง€๋„ ๊ธฐ์ดˆ

์งˆ๋ฌธ 1: SwiftUI๋กœ ๊ธฐ๋ณธ ์ง€๋„๋ฅผ ํ‘œ์‹œํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ฉ๋‹ˆ๊นŒ?

iOS 17๋ถ€ํ„ฐ SwiftUI๋Š” ์ด์ „์˜ UIViewRepresentable ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋Œ€์ฒดํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ Map ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Map ๋ทฐ๋Š” ์นด๋ฉ”๋ผ ์œ„์น˜์™€ ํ‘œ์‹œํ•  ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

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์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: .automatic(์ž๋™ ์กฐ์ •), .region(๊ณ ์ • ์˜์—ญ), .camera(์™„์ „ ์ œ์–ด) ๋˜๋Š” .userLocation(์‚ฌ์šฉ์ž ์ค‘์‹ฌ).

์งˆ๋ฌธ 2: ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ง€๋„ ์Šคํƒ€์ผ์—๋Š” ์–ด๋–ค ๊ฒƒ๋“ค์ด ์žˆ์Šต๋‹ˆ๊นŒ?

MapKit์€ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ์ ํ•ฉํ•œ ์—ฌ๋Ÿฌ ์ง€๋„ ์Šคํƒ€์ผ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. .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(์‚ฌ์šฉ์ž ์ •์˜ ๋ทฐ), 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์€ ๋ชจ๋“  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: ๋‘ ์ง€์  ์‚ฌ์ด์˜ ๊ฒฝ๋กœ๋ฅผ ํ‘œ์‹œํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ฉ๋‹ˆ๊นŒ?

๊ฒฝ๋กœ ๊ณ„์‚ฐ์€ Apple ์„œ๋ฒ„์—์„œ ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด MKDirections๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ‘œ์‹œ๋Š” ๊ทธ ํ›„ 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, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

๊ฒฐ๋ก 

SwiftUI์™€ ํ•จ๊ป˜ํ•˜๋Š” MapKit์€ iOS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ง€๋„๋ฅผ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•œ ํ˜„๋Œ€์ ์ด๊ณ  ๊ฐ•๋ ฅํ•œ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์–ด๋…ธํ…Œ์ด์…˜, ์˜ค๋ฒ„๋ ˆ์ด, ์ง€์˜ค๋กœ์ผ€์ด์…˜ ๋ฐ ์žฅ์†Œ ๊ฒ€์ƒ‰์— ๋Œ€ํ•œ ์ˆ™๋‹ฌ์€ ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ๊ฒฝํ—˜ ๋งŽ์€ iOS ๊ฐœ๋ฐœ์ž๋ฅผ ๋‹๋ณด์ด๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๊ฒ€ํ†  ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • โœ… Map์œผ๋กœ ์ง€๋„๋ฅผ ํ‘œ์‹œํ•˜๊ณ  MapCameraPosition ๊ตฌ์„ฑํ•˜๊ธฐ
  • โœ… ๋‹ค์–‘ํ•œ ์ง€๋„ ์Šคํƒ€์ผ ์‚ฌ์šฉํ•˜๊ธฐ(standard, imagery, hybrid)
  • โœ… ์‚ฌ์šฉ์ž ์ •์˜ Marker ๋ฐ Annotation ์ถ”๊ฐ€ํ•˜๊ธฐ
  • โœ… CLLocationManager๋กœ ์œ„์น˜ ๊ถŒํ•œ ์ฒ˜๋ฆฌํ•˜๊ธฐ
  • โœ… ์‹ค์‹œ๊ฐ„์œผ๋กœ ์œ„์น˜ ์ถ”์ ํ•˜๊ณ  ๊ฒฝ๋กœ ๊ทธ๋ฆฌ๊ธฐ
  • โœ… ์˜ค๋ฒ„๋ ˆ์ด ๊ทธ๋ฆฌ๊ธฐ(MapCircle, MapPolygon, MapPolyline)
  • โœ… MKDirections๋กœ ๊ฒฝ๋กœ ๊ณ„์‚ฐํ•˜๊ธฐ
  • โœ… MKLocalSearch๋กœ ์žฅ์†Œ ๊ฒ€์ƒ‰ ๊ตฌํ˜„ํ•˜๊ธฐ
  • โœ… ์ง€์˜ค์ฝ”๋”ฉ ๋ฐ ์—ญ์ง€์˜ค์ฝ”๋”ฉ ์ˆ˜ํ–‰ํ•˜๊ธฐ
  • โœ… ๋งˆ์ปค ํด๋Ÿฌ์Šคํ„ฐ๋ง์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”ํ•˜๊ธฐ
  • โœ… ๋ชฐ์ž…ํ˜• ๋ณด๊ธฐ๋ฅผ ์œ„ํ•œ Look Around ํ†ตํ•ฉํ•˜๊ธฐ

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#mapkit
#swiftui
#ios
#geolocation
#interview

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ