새로 업데이트 된 MapKit 을 활용하여 길찾기, 네비게이션을 구현해 볼 것이다. 아래와 같이 길찾기를 하면 길찾기 결과가 맵에 보이고, 네비게이션을 누르면 네비게이션 안내가 나오게 된다.
맵 구현
Map { } 으로 맵을 간단하게 구현할 수 있다. 시작점, 도착점을 CLLocationCoordinate2D 형식으로 넣어준다. MKRoute 형식의 route 라는 변수도 만들어 준다.
import SwiftUI
import MapKit
struct ContentView: View {
@State private var route: MKRoute?
@State private var name = ""
@State private var time = 0.0
@State private var distance = 0.0
@State private var navigation: [String] = []
@State private var showNav = false
private let start = CLLocationCoordinate2D(latitude: 37.39535, longitude: 127.11259)
private let end = CLLocationCoordinate2D(latitude: 37.34591, longitude: 127.09083)
var body: some View {
Map {
UserAnnotation()
Marker("시작점", coordinate: start)
Marker("도착점", coordinate: end)
if let route {
MapPolyline(route)
.stroke(Color.cyan, style: StrokeStyle(lineWidth: 6, lineCap: .round, lineJoin: .round))
}
}
.mapControls {
MapUserLocationButton()
MapCompass()
}
...
길찾기 함수 구현
source, destination 을 설정해준뒤 MKDirections(request: ) 를 통해 결과값을 받는다. MKRoute 를 MapPolyline 에 넣어주고, 나머지 name, time, distance, navigation 을 넣어주면 된다.
@State private var route: MKRoute?
@State private var name = ""
@State private var time = 0.0
@State private var distance = 0.0
@State private var navigation: [String] = []
private func getRoute() {
route = nil
if !navigation.isEmpty {
navigation = []
}
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: start))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: end))
request.transportType = .walking
Task {
let directions = MKDirections(request: request)
let response = try? await directions.calculate()
if let result = response?.routes.first {
DispatchQueue.main.async {
self.route = result
self.name = result.name
self.time = result.expectedTravelTime
self.distance = result.distance
for step in result.steps {
let distance = step.distance
let instructions = step.instructions
let stepInfo = instructions.isEmpty ? "\(distance)미터" : "\(instructions) \(distance)미터"
self.navigation.append(stepInfo)
}
}
}
}
}
전체코드
import SwiftUI
import MapKit
struct ContentView: View {
private let locationManager = CLLocationManager()
@State private var route: MKRoute?
@State private var name = ""
@State private var time = 0.0
@State private var distance = 0.0
@State private var navigation: [String] = []
@State private var showNav = false
private let start = CLLocationCoordinate2D(latitude: 37.39535, longitude: 127.11259)
private let end = CLLocationCoordinate2D(latitude: 37.34591, longitude: 127.09083)
var body: some View {
Map {
UserAnnotation()
Marker("시작점", coordinate: start)
Marker("도착점", coordinate: end)
if let route {
MapPolyline(route)
.stroke(Color.cyan, style: StrokeStyle(lineWidth: 6, lineCap: .round, lineJoin: .round))
}
}
.mapControls {
MapUserLocationButton()
MapCompass()
}
.onAppear {
locationManager.requestWhenInUseAuthorization()
}
.safeAreaInset(edge: .bottom) {
VStack {
Button {
withAnimation {
getRoute()
}
} label: {
HStack {
Image(systemName: "arrow.triangle.turn.up.right.circle.fill")
Text("길찾기")
}
}
.buttonStyle(CustomButtonStyle())
Button {
showNav.toggle()
} label: {
HStack {
Image(systemName: "map.fill")
Text("네비게이션 보기")
}
}
.buttonStyle(CustomButtonStyle())
}
}
.sheet(isPresented: $showNav) {
NavigationStack {
List {
Section("기본정보") {
Text("이름 : \(name)")
Text("시간 : \(Int(time/60))분")
Text("거리 : \(distance/1000, specifier: "%.1f")km")
}
Section("네비게이션") {
if navigation.isEmpty {
Text("값이 없습니다.")
} else {
ForEach(navigation, id: \.self) { nav in
Text(nav)
}
}
}
}
.navigationTitle("네비게이션")
.navigationBarTitleDisplayMode(.inline)
}
}
}
private func getRoute() {
route = nil
if !navigation.isEmpty {
navigation = []
}
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: start))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: end))
request.transportType = .walking
Task {
let directions = MKDirections(request: request)
let response = try? await directions.calculate()
if let result = response?.routes.first {
DispatchQueue.main.async {
self.route = result
self.name = result.name
self.time = result.expectedTravelTime
self.distance = result.distance
for step in result.steps {
let distance = step.distance
let instructions = step.instructions
let stepInfo = instructions.isEmpty ? "\(distance)미터" : "\(instructions) \(distance)미터"
self.navigation.append(stepInfo)
}
}
}
}
}
}
struct CustomButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(maxWidth: .infinity)
.padding()
.scaleEffect(configuration.isPressed ? 0.97 : 1)
.animation(.bouncy, value: configuration.isPressed)
.background {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.foregroundStyle(.ultraThinMaterial)
}
.background {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.stroke(lineWidth: 1)
.foregroundStyle(configuration.isPressed ? .cyan : .gray)
}
.padding(.horizontal)
}
}