1
votes

I’ve got an MKMapView to animate a line by adding a line, removing it, adding a minor segment and re-adding it to the map. However, this puts a lot of overhead on the phone and doesn’t look the best. I noticed Google Maps and Uber have cleanly animated lines for showing routes that run smoothly no matter what the length or route type. Does anyone have any suggestions for a solution which is less energy-draining and looks cleaner?

Thanks, SebO.

1

1 Answers

0
votes
  1. An array of coordinates will be needed. If you have only beginning and end coordinates, get array of coordinates using below code

    func getPointsOnRoute(from: CLLocation?, to: CLLocation?, on mapView: MKMapView?) -> [CLLocation]? {
    let NUMBER_OF_PIXELS_TO_SKIP: Int = 120
    //lower number will give a more smooth animation, but will result in more layers
    var ret = [Any]()
    
    var fromPoint: CGPoint? = nil
    if let aCoordinate = from?.coordinate {
        fromPoint = mapView?.convert(aCoordinate, toPointTo: mapView)
    }
    var toPoint: CGPoint? = nil
    if let aCoordinate = to?.coordinate {
        toPoint = mapView?.convert(aCoordinate, toPointTo: mapView)
    }
    let allPixels = getAllPoints(from: fromPoint!, to: toPoint!)
    var i = 0
    while i < (allPixels?.count)! {
        let pointVal = allPixels![i] as? NSValue
        ret.append(point(toLocation: mapView, from: (pointVal?.cgPointValue)!)!)
        i += NUMBER_OF_PIXELS_TO_SKIP
    }
    ret.append(point(toLocation: mapView, from: toPoint!)!)
    return ret as? [CLLocation] }
    
  2. Having array of coordinates add rendering of the overlays in MKMapViewDelegate’s delegate method — mapView(_:rendererFor:).

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    guard let polyline = overlay as? MKPolyline else {
            return MKOverlayRenderer()
        }
    
        let polylineRenderer = MKPolylineRenderer(overlay: polyline)
        polylineRenderer.strokeColor = .black
        polylineRenderer.lineWidth = 2
        return polylineRenderer
    }
    mapView.addOverlay(polyline) // add it to mapview
    
    1. render the polyline in small segments to create the animation effect
    var drawingTimer: Timer?
    // ....// Somewhere in your View Controller
    func animate(route: [CLLocationCoordinate2D], duration: TimeInterval, completion: (() -> Void)?) {
            guard route.count > 0 else { return }
            var currentStep = 1
            let totalSteps = route.count
            let stepDrawDuration = duration/TimeInterval(totalSteps)
            var previousSegment: MKPolyline?
    
            drawingTimer = Timer.scheduledTimer(withTimeInterval: stepDrawDuration, repeats: true) { [weak self] timer in
                guard let self = self else {
                    // Invalidate animation if we can't retain self
                    timer.invalidate()
                    completion?()
                    return
                }
    
                if let previous = previousSegment {
                    // Remove last drawn segment if needed.
                    self.mapView.removeOverlay(previous)
                    previousSegment = nil
                }
    
                guard currentStep < totalSteps else {
                    // If this is the last animation step...
                    let finalPolyline = MKPolyline(coordinates: route, count: route.count)
                    self.mapView.addOverlay(finalPolyline)
                    // Assign the final polyline instance to the class property.
                    self.polyline = finalPolyline
                    timer.invalidate()
                    completion?()
                    return
                }
    
                // Animation step.
                // The current segment to draw consists of a coordinate array from 0 to the 'currentStep' taken from the route.
                let subCoordinates = Array(route.prefix(upTo: currentStep))
                let currentSegment = MKPolyline(coordinates: subCoordinates, count: subCoordinates.count)
                self.mapView.addOverlay(currentSegment)
    
                previousSegment = currentSegment
                currentStep += 1
            }
        }