We struggled to create our own iOS map app because drawing routes on top of MapKit in SwiftUI was extremely challenging.
Issue 1: Misuse of overlay
Issue Description
The overlay
method in SwiftUI
‘s Map
component expects elements that conform to ShapeStyle
, but an attempt was made to use it to render a MKPolyline
overlay, causing the error: type '() -> ()' cannot conform to 'ShapeStyle'
.
Problematic Code Example
Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true)
.overlay {
if let polyline = routePolyline {
MapPolyline(polyline)
.stroke(Color.blue, lineWidth: 5)
}
}
Solution
Since the overlay
method in SwiftUI
expects ShapeStyle
elements, it is not suitable for displaying MKPolyline
on a Map
. To correctly display an MKPolyline
, you need to integrate MKMapView
into SwiftUI using UIViewRepresentable
and use MKPolylineRenderer
to render the polyline.
Correct Code Example
struct MapView: UIViewRepresentable {
@Binding var region: MKCoordinateRegion
@Binding var routePolyline: MKPolyline?
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(parent: MapView) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = .blue
renderer.lineWidth = 5
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.setRegion(region, animated: true)
mapView.showsUserLocation = true
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.setRegion(region, animated: true)
mapView.removeOverlays(mapView.overlays)
if let polyline = routePolyline {
mapView.addOverlay(polyline)
}
}
}
Issue 2: Misuse of annotationItems
Issue Description
MKPolyline
is not an annotation (pin or marker), but rather a route (overlay) on the map. However, annotationItems
is a property for annotation items, so trying to use MKPolyline
here caused an error. Additionally, an error occurred because MKPolyline
does not conform to Identifiable
.
Problematic Code Example
Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, annotationItems: routePolyline != nil ? [routePolyline!] : []) { polyline in
MapPolyline(polyline)
.stroke(Color.blue, lineWidth: 5)
}
Solution
Instead of using annotationItems
, which is intended for annotations, treat MKPolyline
as an overlay on the map. This can be done by using UIViewRepresentable
to wrap an MKMapView
and add the polyline as an overlay.
Correct Code Example
struct ContentView: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
@State private var routePolyline: MKPolyline?
var body: some View {
VStack {
MapView(region: $region, routePolyline: $routePolyline)
.frame(height: 400)
.edgesIgnoringSafeArea(.top)
Button(action: {
searchRoute()
}) {
Text("Search Route")
}
.padding()
Spacer()
}
}
func searchRoute() {
let startCoordinate = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
let destinationCoordinate = CLLocationCoordinate2D(latitude: 37.7849, longitude: -122.4294)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: startCoordinate))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destinationCoordinate))
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { response, error in
if let error = error {
print("Route search error: \(error.localizedDescription)")
return
}
if let route = response?.routes.first {
self.routePolyline = route.polyline
self.region = MKCoordinateRegion(route.polyline.boundingMapRect)
}
}
}
}
Issue 3: MKPolyline
Does Not Conform to Identifiable
Issue Description
An error was encountered when trying to use annotationItems
because MKPolyline
does not conform to Identifiable
. This is closely related to the issue of treating MKPolyline
as an annotation, which is incorrect.
Solution
Avoid using MKPolyline
as an annotation. Instead, handle it properly as an overlay using UIViewRepresentable
to integrate MKMapView
.
Conclusion
The SwiftUI
Map
component is suitable for basic map displays but lacks support for rendering custom overlays such as MKPolyline
. The recommended approach is to integrate MKMapView
into SwiftUI using UIViewRepresentable
, which allows access to advanced MapKit features and ensures safe and flexible implementation of map functionality.
Full Sample Code
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
@Binding var region: MKCoordinateRegion
@Binding var routePolyline: MKPolyline?
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(parent: MapView) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = .blue
renderer.lineWidth = 5
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.setRegion(region, animated: true)
mapView.showsUserLocation = true
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.setRegion(region, animated: true)
mapView.removeOverlays(mapView.overlays)
if let polyline = routePolyline {
mapView.addOverlay(polyline)
}
}
}
struct ContentView: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
@State private var routePolyline: MKPolyline?
var body: some View {
VStack {
MapView(region: $region, routePolyline: $routePolyline)
.frame(height: 400)
.edgesIgnoringSafeArea(.top)
Button(action: {
searchRoute()
}) {
Text("Search")
}
.padding()
Spacer()
}
}
func searchRoute() {
let startCoordinate = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
let destinationCoordinate = CLLocationCoordinate2D(latitude: 37.7849, longitude: -122.4294)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: startCoordinate))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destinationCoordinate))
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { response, error in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
if let route = response?.routes.first {
self.routePolyline = route.polyline
self.region = MKCoordinateRegion(route.polyline.boundingMapRect)
}
}
}
}
#Preview {
ContentView()
}
コメントを残す
コメントを投稿するにはログインしてください。