🌐 KO

πŸ—ΊοΈ Mastering MapKit

Build location-based apps with Apple Maps β€” markers, routes, 3D buildings and more!

⭐ Difficulty: ⭐⭐ ⏱️ Est. Time: 1-2h πŸ“‚ App Services

✨ MapKit is?

MapKit is a framework for integrating Apple Maps into your app. Easily implemented with SwiftUI's Map view.

πŸ—ΊοΈ Basic Map Display (SwiftUI)

BasicMap.swift
import SwiftUI
import MapKit

struct ContentView: View {
    @State private var position: MapCameraPosition = .region(
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780),
            span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
        )
    )

    var body: some View {
        Map(position: $position)
    }
}

// Map μŠ€νƒ€μΌ
Map(position: $position) {
}
.mapStyle(.standard)  // .standard, .hybrid, .imagery

πŸ“ Adding Markers

Markers.swift
import SwiftUI
import MapKit

struct Location: Identifiable {
    let id = UUID()
    let name: String
    let coordinate: CLLocationCoordinate2D
}

struct MapWithMarkers: View {
    @State private var position: MapCameraPosition = .automatic

    let locations = [
        Location(name: "경볡ꢁ", coordinate: CLLocationCoordinate2D(latitude: 37.5788, longitude: 126.9770)),
        Location(name: "λ‚¨μ‚°νƒ€μ›Œ", coordinate: CLLocationCoordinate2D(latitude: 37.5512, longitude: 126.9882)),
        Location(name: "ν•œκ°•κ³΅μ›", coordinate: CLLocationCoordinate2D(latitude: 37.5326, longitude: 126.9610))
    ]

    var body: some View {
        Map(position: $position) {
            ForEach(locations) { location in
                // Default 마컀
                Marker(location.name, coordinate: location.coordinate)
            }
        }
    }
}

// Custom 색상 마컀
Marker("λ ˆμŠ€ν† λž‘", systemImage: "fork.knife", coordinate: coord)
    .tint(.red)

// μ–΄λ…Έν…Œμ΄μ…˜ (더 λ§Žμ€ μ»€μŠ€ν„°λ§ˆμ΄μ§•)
Annotation("카페", coordinate: coord) {
    ZStack {
        Circle()
            .fill(Color.blue)
            .frame(width: 30, height: 30)
        Image(systemName: "cup.and.saucer.fill")
            .foregroundColor(.white)
    }
}

🎯 Displaying User Location

UserLocation.swift
import SwiftUI
import MapKit

struct MapWithUserLocation: View {
    @State private var position: MapCameraPosition = .userLocation(fallback: .automatic)

    var body: some View {
        Map(position: $position) {
            UserAnnotation()  // User μœ„μΉ˜ 마컀
        }
        .mapControls {
            MapUserLocationButton()  // λ‚΄ μœ„μΉ˜ λ²„νŠΌ
            MapCompass()  // λ‚˜μΉ¨λ°˜
            MapScaleView()  // μΆ•μ²™
        }
    }
}

πŸ›£οΈ Showing Directions

Route.swift
import MapKit

func calculateRoute(from start: CLLocationCoordinate2D, to end: CLLocationCoordinate2D) async throws -> MKRoute? {
    let request = MKDirections.Request()
    request.source = MKMapItem(placemark: MKPlacemark(coordinate: start))
    request.destination = MKMapItem(placemark: MKPlacemark(coordinate: end))
    request.transportType = .automobile  // .walking, .transit

    let directions = MKDirections(request: request)
    let response = try await directions.calculate()

    return response.routes.first
}

// SwiftUIμ—μ„œ 경둜 ν‘œμ‹œ
struct RouteMapView: View {
    @State private var route: MKRoute?
    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        Map(position: $position) {
            if let route = route {
                MapPolyline(route.polyline)
                    .stroke(Color.blue, lineWidth: 5)
            }

            // 좜발/도착 마컀
            Marker("좜발", systemImage: "mappin.circle.fill", coordinate: start)
                .tint(.green)
            Marker("도착", systemImage: "flag.fill", coordinate: end)
                .tint(.red)
        }
        .task {
            route = try? await calculateRoute(from: start, to: end)
        }
    }
}

πŸ” Place Search

Search.swift
import MapKit

func searchPlaces(query: String, region: MKCoordinateRegion) async throws -> [MKMapItem] {
    let request = MKLocalSearch.Request()
    request.naturalLanguageQuery = query  // "카페", "λ ˆμŠ€ν† λž‘" λ“±
    request.region = region

    let search = MKLocalSearch(request: request)
    let response = try await search.start()

    return response.mapItems
}

// SwiftUI 톡합
struct SearchMapView: View {
    @State private var searchResults: [MKMapItem] = []
    @State private var searchText = ""
    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        VStack {
            TextField("μž₯μ†Œ 검색", text: $searchText)
                .textFieldStyle(.roundedBorder)
                .padding()
                .onSubmit {
                    Task { await search() }
                }

            Map(position: $position) {
                ForEach(searchResults, id: \.self) { item in
                    Marker(item.name ?? "μž₯μ†Œ", coordinate: item.placemark.coordinate)
                }
            }
        }
    }

    func search() async {
        let region = MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780),
            span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
        )

        searchResults = (try? await searchPlaces(query: searchText, region: region)) ?? []
    }
}

πŸ“ Reverse Geocoding

Geocoding.swift
import CoreLocation

func reverseGeocode(_ coordinate: CLLocationCoordinate2D) async throws -> String {
    let geocoder = CLGeocoder()
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)

    let placemarks = try await geocoder.reverseGeocodeLocation(location)

    guard let placemark = placemarks.first else {
        return "μ£Όμ†Œ μ—†μŒ"
    }

    let address = [
        placemark.thoroughfare,  // λ„λ‘œλͺ…
        placemark.locality,  // μ‹œ/ꡬ
        placemark.country  // κ΅­κ°€
    ].compactMap { $0 }.joined(separator: ", ")

    return address
}

// μ •μ§€μ˜€μ½”λ”© (μ£Όμ†Œ β†’ μ’Œν‘œ)
func geocode(address: String) async throws -> CLLocationCoordinate2D? {
    let geocoder = CLGeocoder()
    let placemarks = try await geocoder.geocodeAddressString(address)
    return placemarks.first?.location?.coordinate
}

🎨 Camera Control

CameraControl.swift
import SwiftUI
import MapKit

struct CameraControlView: View {
    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        VStack {
            Map(position: $position)

            HStack {
                // νŠΉμ • μ’Œν‘œλ‘œ 이동
                Button("μ„œμšΈ") {
                    position = .region(
                        MKCoordinateRegion(
                            center: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780),
                            span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
                        )
                    )
                }

                // Camera each도 쑰절 (3D)
                Button("3D λ·°") {
                    position = .camera(
                        MapCamera(
                            centerCoordinate: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780),
                            distance: 1000,  // 고도
                            heading: 45,  // λ°©ν–₯
                            pitch: 60  // 기울기
                        )
                    )
                }
            }
            .padding()
        }
    }
}

🏒 Map Styles & 3D Buildings

MapStyles.swift
Map(position: $position) {
}
.mapStyle(.standard)  // Default
.mapStyle(.hybrid)  // μœ„μ„± + λ„λ‘œ
.mapStyle(.imagery)  // μœ„μ„±λ§Œ

// 3D 건물 ν‘œμ‹œ
.mapStyle(.standard(elevation: .realistic))

// POI (관심 μž₯μ†Œ) ν•„ν„°
.mapStyle(.standard(pointsOfInterest: .including([.cafe, .restaurant])))

πŸ“± UIKit Integration (MKMapView)

MKMapView.swift
import SwiftUI
import MapKit

struct MapViewRepresentable: UIViewRepresentable {
    @Binding var region: MKCoordinateRegion

    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.delegate = context.coordinator
        return mapView
    }

    func updateUIView(_ mapView: MKMapView, context: Context) {
        mapView.setRegion(region, animated: true)
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, MKMapViewDelegate {
        var parent: MapViewRepresentable

        init(_ parent: MapViewRepresentable) {
            self.parent = parent
        }

        func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
            parent.region = mapView.region
        }
    }
}

🎯 Selectable Markers

SelectableMarkers.swift
struct SelectableMapView: View {
    @State private var selectedLocation: Location?
    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        Map(position: $position, selection: $selectedLocation) {
            ForEach(locations) { location in
                Marker(location.name, coordinate: location.coordinate)
                    .tag(location)
            }
        }
        .sheet(item: $selectedLocation) { location in
            VStack {
                Text(location.name)
                    .font(.title)
                Text("μœ„λ„: \(location.coordinate.latitude)")
                Text("경도: \(location.coordinate.longitude)")
            }
            .padding()
        }
    }
}

πŸ’‘ HIG Checklist
βœ… μ‚¬μš©μž μœ„μΉ˜ ν‘œμ‹œ μ‹œ Permission Request
βœ… Loading State Display
βœ… Network Error Handling
βœ… μ μ ˆν•œ 쀌 레벨 μ„€μ •
βœ… λ§ˆμ»€λŠ” λͺ…ν™•ν•˜κ³  ꡬ뢄 κ°€λŠ₯ν•˜κ²Œ

πŸ“¦ Learning Resources

πŸ’»
GitHub Project
🍎
Apple HIG Docs
πŸ“–
Apple Official Docs

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation πŸ’» Sample Code 🎬 WWDC Sessions