๐บ๏ธ MapKit ์์ ์ ๋ณต
Apple ์ง๋๋ก ์์น ๊ธฐ๋ฐ ์ฑ์ ๋ง๋์ธ์. ๋ง์ปค, ๊ฒฝ๋ก, 3D ๊ฑด๋ฌผ๊น์ง!
โจ MapKit์ด๋?
MapKit์ Apple Maps๋ฅผ ์ฑ์ ํตํฉํ ์ ์๋ ํ๋ ์์ํฌ์ ๋๋ค. SwiftUI์ Map ๋ทฐ๋ก ๊ฐ๋จํ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
๐บ๏ธ ๊ธฐ๋ณธ ์ง๋ ํ์ (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(position: $position) { } .mapStyle(.standard) // .standard, .hybrid, .imagery
๐ ๋ง์ปค ์ถ๊ฐ
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 // ๊ธฐ๋ณธ ๋ง์ปค Marker(location.name, coordinate: location.coordinate) } } } } // ์ปค์คํ ์์ ๋ง์ปค 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) } }
๐ฏ ์ฌ์ฉ์ ์์น ํ์
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() // ์ฌ์ฉ์ ์์น ๋ง์ปค } .mapControls { MapUserLocationButton() // ๋ด ์์น ๋ฒํผ MapCompass() // ๋์นจ๋ฐ MapScaleView() // ์ถ์ฒ } } }
๐ฃ๏ธ ๊ฒฝ๋ก ํ์ (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) } } }
๐ ์ฅ์ ๊ฒ์ (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)) ?? [] } }
๐ ์ญ์ง์ค์ฝ๋ฉ (์ขํ โ ์ฃผ์)
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 }
๐จ ์นด๋ฉ๋ผ ์ ์ด
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) ) ) } // ์นด๋ฉ๋ผ ๊ฐ๋ ์กฐ์ (3D) Button("3D ๋ทฐ") { position = .camera( MapCamera( centerCoordinate: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780), distance: 1000, // ๊ณ ๋ heading: 45, // ๋ฐฉํฅ pitch: 60 // ๊ธฐ์ธ๊ธฐ ) ) } } .padding() } } }
๐ข ๋งต ์คํ์ผ & 3D ๊ฑด๋ฌผ
MapStyles.swift
Map(position: $position) { } .mapStyle(.standard) // ๊ธฐ๋ณธ .mapStyle(.hybrid) // ์์ฑ + ๋๋ก .mapStyle(.imagery) // ์์ฑ๋ง // 3D ๊ฑด๋ฌผ ํ์ .mapStyle(.standard(elevation: .realistic)) // POI (๊ด์ฌ ์ฅ์) ํํฐ .mapStyle(.standard(pointsOfInterest: .including([.cafe, .restaurant])))
๐ฑ UIKit ํตํฉ (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 } } }
๐ฏ ์ ํ ๊ฐ๋ฅํ ๋ง์ปค
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 ์ฒดํฌ๋ฆฌ์คํธ
โ
์ฌ์ฉ์ ์์น ํ์ ์ ๊ถํ ์์ฒญ
โ
๋ก๋ฉ ์ํ ํ์
โ
๋คํธ์ํฌ ์๋ฌ ์ฒ๋ฆฌ
โ
์ ์ ํ ์ค ๋ ๋ฒจ ์ค์
โ
๋ง์ปค๋ ๋ช
ํํ๊ณ ๊ตฌ๋ถ ๊ฐ๋ฅํ๊ฒ