M1X
M1X

Reputation: 5354

MapKit: Route not being displayed between two annotations

Im trying to display a route between two annotations.

The annotations and the region work fine but the route won't show up and I have no idea why It looks like the route is not being rendered at all. I'm sure that the route exists because I tried to print it and it is in the directionResponse.routes Any suggestions?

I'm using SwiftUI

Then this is included in a parent view.

import SwiftUI
import MapKit
import FirebaseFirestore

struct MapView: UIViewRepresentable {
    var packageLocation: GeoPoint
    var destination: GeoPoint
    var driverLocation = CLLocationCoordinate2D()

    func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
        MKMapView()
    }

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        let renderer = MKPolygonRenderer(overlay: overlay)
        renderer.strokeColor = .blue
        renderer.lineWidth = 2.0
        return renderer
    }


    func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
        let requestLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: packageLocation.latitude, longitude: packageLocation.longitude)
        let destinationLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: destination.latitude, longitude: destination.longitude)

        //let span = MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
        //let region = MKCoordinateRegion(center: requestLocation, span: span)
        //uiView.setRegion(region, animated: true)

        let annotation = MKPointAnnotation()
        annotation.coordinate = requestLocation
        annotation.title = "Package Title"
        uiView.addAnnotation(annotation)

        let annotation2 = MKPointAnnotation()
        annotation2.coordinate = destinationLocation
        annotation2.title = "Destiantion"
        uiView.addAnnotation(annotation2)

        let sourcePlacemark = MKPlacemark(coordinate: requestLocation)
        let destinationPlacemark = MKPlacemark(coordinate: destinationLocation)


        let directionRequest = MKDirections.Request()
        directionRequest.source = MKMapItem(placemark: sourcePlacemark)
        directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
        directionRequest.transportType = .automobile

        let directions = MKDirections(request: directionRequest)

        directions.calculate { (response, error) in
            guard let directionResponse = response else {
                if let error = error {
                    print(error.localizedDescription)
                }
                return
            }
            print(directionResponse)

            let route = directionResponse.routes[0]
            uiView.addOverlay(route.polyline, level: .aboveRoads)

            let rect = route.polyline.boundingMapRect
            uiView.setRegion(MKCoordinateRegion(rect), animated: true)
        }

    }
}

Upvotes: 1

Views: 1208

Answers (1)

Andrew
Andrew

Reputation: 28539

You've almost got it.

The one issue that you need to resolve is the use of the MKMapView delegate functions.

The easiest way to do that is to subclass MKMapView and make your own map view that has conforms to MKMapViewDelegate.

Firstly, create your own map view, subclassing MKMapView and conforming to MKMapViewDelegate. At the moment you're only really using the rendererFor overlay delegate method so I'll just implement that, but you can add other methods if you require them.

class WrappableMapView: MKMapView, MKMapViewDelegate {

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        let renderer = MKPolylineRenderer(overlay: overlay)
        renderer.strokeColor = .red
        renderer.lineWidth = 4.0
        return renderer
    }
}

Then you need to update your UIViewRepresentable to use the new WrappableMapView that you just created. I have gone for making a functional example, so here I am passing in the request and destination locations. You can handle this how you want but at least this will give you something that works.

struct MyMapView: UIViewRepresentable {

    @Binding var requestLocation: CLLocationCoordinate2D
    @Binding var destinationLocation: CLLocationCoordinate2D

    private let mapView = WrappableMapView()

    func makeUIView(context: UIViewRepresentableContext<MyMapView>) -> WrappableMapView {
        mapView.delegate = mapView // make sure we set our delegate to be the mapView we just created
        return mapView
    }

    func updateUIView(_ uiView: WrappableMapView, context: UIViewRepresentableContext<MyMapView>) {

        let requestAnnotation = MKPointAnnotation()
        requestAnnotation.coordinate = requestLocation
        requestAnnotation.title = "Package Title"
        uiView.addAnnotation(requestAnnotation)

        let destinationAnnotation = MKPointAnnotation()
        destinationAnnotation.coordinate = destinationLocation
        destinationAnnotation.title = "Destination"
        uiView.addAnnotation(destinationAnnotation)

        let requestPlacemark = MKPlacemark(coordinate: requestLocation)
        let destinationPlacemark = MKPlacemark(coordinate: destinationLocation)

        let directionRequest = MKDirections.Request()
        directionRequest.source = MKMapItem(placemark: requestPlacemark)
        directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
        directionRequest.transportType = .automobile

        let directions = MKDirections(request: directionRequest)
        directions.calculate { response, error in
            guard let response = response else { return }

            let route = response.routes[0]
            uiView.addOverlay(route.polyline, level: .aboveRoads)

            let rect = route.polyline.boundingMapRect
            uiView.setRegion(MKCoordinateRegion(rect), animated: true)

            // if you want insets use this instead of setRegion
            //  uiView.setVisibleMapRect(rect, edgePadding: .init(top: 50.0, left: 50.0, bottom: 50.0, right: 50.0), animated: true)
        }

    }
}

Finally we can put it all together with a ContentView that shows it works:

struct ContentView: View {

    @State var requestLocation = CLLocationCoordinate2D(latitude: 51.509865, longitude:  -0.118092)
    @State var destinationLocation = CLLocationCoordinate2D(latitude: 51.501266, longitude: -0.093210)

    var body: some View {
        MyMapView(requestLocation: $requestLocation, destinationLocation: $destinationLocation)
    }
}

This is what it should look like:

Map View with route


One thing to note, using the rendererFor overlay delegate function in the simulator causes an error. This only happens in the simulator and not on device, so don't be surprised if you see an error message like this in the console.

2019-11-08 18:50:30.034066+0000 StackOverflow[80354:9526181] Compiler error: Invalid library file

Upvotes: 4

Related Questions