SwiftUI で MapKit 上にルートを描画するのは非常に困難だったため、独自の iOS マップ アプリを作成するのに苦労しました。
問題1: overlay
の使用方法
問題の詳細
SwiftUI の Map コンポーネントのオーバーレイ メソッドは ShapeStyle に準拠する要素を想定していますが、これを使用して MKPolyline オーバーレイをレンダリングしようとしたため、次のエラーが発生しました: type '() -> ()' cannot conform to 'ShapeStyle'
問題が発生するコード
Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true)
.overlay {
if let polyline = routePolyline {
MapPolyline(polyline)
.stroke(Color.blue, lineWidth: 5)
}
}
解決方法
SwiftUI のオーバーレイ メソッドは ShapeStyle 要素を想定しているため、マップ上に MKPolyline を表示するのには適していません。
MKPolyline を正しく表示するには、UIViewRepresentable を使用して MKMapView を SwiftUI に統合し、MKPolylineRenderer を使用してポリラインをレンダリングする必要があります。
この問題の修正後
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)
}
}
}
問題2: annotationItems
の使用方法
問題の詳細
MKPolyline は注釈 (ピンやマーカー) ではなく、地図上のルート (オーバーレイ) です。ただし、annotationItems は注釈のプロパティなので、ここで MKPolyline を使用しようとするとエラーが発生します。また、MKPolyline は Identifiable に準拠していないため、エラーが発生しました。
問題が発生するコード
Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, annotationItems: routePolyline != nil ? [routePolyline!] : []) { polyline in
MapPolyline(polyline)
.stroke(Color.blue, lineWidth: 5)
}
解決方法
注釈用のannotationItemsを使用する代わりに、MKPolylineをマップ上のオーバーレイとして扱います。これは、UIViewRepresentableを使用してMKMapViewをラップし、ポリラインをオーバーレイとして追加することで実行できます。
この問題の修正後
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)
}
}
}
}
問題3: MKPolyline
は Identifiable
に準拠していません
問題の詳細
(先ほども言いましたが、) MKPolyline が Identifiable に準拠していないため、annotationItems を使用しようとしたときにエラーが発生しました。これは、MKPolyline を注釈として扱うという問題2と密接に関連しており、これは正しくありません。
解決方法
Avoid using MKPolyline
as an annotation. Instead, handle it properly as an overlay using UIViewRepresentable
to integrate MKMapView
.
結論
SwiftUI Map コンポーネントは基本的なマップ表示に適していますが、MKPolyline などのカスタム オーバーレイのレンダリングはサポートされていません。推奨されるアプローチは、UIViewRepresentable を使用して MKMapView を SwiftUI に統合することです。これにより、高度な MapKit 機能にアクセスでき、マップ機能の安全で柔軟な実装が保証されます。
完全なサンプルコード
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()
}
コメントを残す
コメントを投稿するにはログインしてください。