MKMapView : Animate Drawing of MKPolyline Between Locations - ios

I have drawn straight line between locations using MKPolyline in MKMapView
let locations:[CLLocationCoordinate2D] = ...
let polyLine = MKPolyline(coordinates: locations, count: locations.count)
mapView.add(polyLine)
// MKMapViewDelegate
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.red
renderer.lineWidth = 4.0
return renderer
}
Now I want show drawing animation of line from one coordinate to another instead of already drew lines. Is there any possibility to do that? Any idea will be appreciated.

From your array of coordinates draw a poly line between first item and the second say in 0.1 seconds , then draw with the first item to the third and so on until draw with the first item to the last one with a timer you can have animated draw from source to destination

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] }
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
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
}
}

Related

MK Directions Request - direction line is not shown

I have successfully implemented location of a point of interest and my location. Both is shown. Now, I would like to get calculated the route between two points and a blue line should be shown. Unfortunately, when I am clicking on the button, no line is being shown.
I really appreciate help/hints. Thanks so much.
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate
{
//outlet variable is used for establishing a connection with the
// map view in the storyboard
#IBOutlet var mapView: MKMapView!
var spot = Spot()
let locationManager = CLLocationManager()
var currentPlacemark:CLPlacemark?// it is used to save the selected spot
override func viewDidLoad()
{
super.viewDidLoad()
//request for a user's authorization for lacation services
locationManager.requestWhenInUseAuthorization()
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.authorizedWhenInUse
{
mapView.showsUserLocation = true
}
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(spot.location,
completionHandler:
{ placemarks, error in
if let error = error {
print(error)
return
}
if let placemarks = placemarks{
//get the first placemark
let placemark = placemarks[0]
// value of current Placemark
self.currentPlacemark = placemark
// add annotation
let annotation = MKPointAnnotation()
annotation.title = self.spot.name
annotation.subtitle = self.spot.type
if let location = placemark.location{
annotation.coordinate = location.coordinate
//display the annotation
self.mapView.showAnnotations([annotation],animated:true)
self.mapView.selectAnnotation(annotation, animated: true)
}
}
})
mapView.showsCompass = true
mapView.showsTraffic = true
mapView.showsScale = true
// we want to show the users location
mapView.showsUserLocation = true
}
#IBAction func showDirection(sender:AnyObject)
{
// we make sure if current placemark contains a value using a guard statement. Otherwise just
// skip everything
guard let currentPlacemark = currentPlacemark else
{
return
}
// creating an instance of MKDirectionsRequest to request directions
let directionRequest = MKDirectionsRequest()
// set the source(where the user currently is) and destination of the route
directionRequest.source = MKMapItem.forCurrentLocation()// retrieving the current location
let destinationPlacemark = MKPlacemark(placemark:currentPlacemark)
directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
directionRequest.transportType = MKDirectionsTransportType.automobile// later change for transit
// calculate the direction
let directions = MKDirections(request: directionRequest)
// this method initiates an asynchronous request for directions and calls
// your completion handler when the request is conpleted. The MKDirections object
//passes my request to the Apple servers ans asks for route-based directions data
directions.calculate { (routeRepsonse, routeError) -> Void in
guard let routeResponse = routeRepsonse else
{
if let routeError = routeError
{
print("Error:\(routeError)")
}
return
}
let route = routeRepsonse?.routes[0]// provides a container for saving the route information so that the routes are saved in the routes property
// The detailed route geometry is e.g. route.polyline is represented by an MKPolyline object
// the add level method is used to add an MKPolyline object to the existing map view
self.mapView.add((route?.polyline)!,level: MKOverlayLevel.aboveRoads)
}
}
// implementing a mapView method which draws the route
func mapView(_mapView:MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer
{
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 3.0
return renderer
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources hat can be recreated.
}
}
Maybe delegate method name is wrong. Rename
func mapView(_mapView:MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer
with
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer

Drawing a polyline based on previous locations, to track a users path

I've used the following code in Xcode to display a poly line of my past locations. But it does not seem to appear. Any help will be appreciated.
Im not sure if my method to store the locations in an array is wrong or my code to display the poly line is wrong.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
theLabel.text = "\(locations[0])"
storedLocations.append(locations[0] as CLLocation)
let location = locations[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.01, 0.01)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
mapKitView.setRegion(region, animated:true)
print (location.speed)
print (location.altitude)
print (location.coordinate)
print (location.course) //Direction you are heading in NSEW
altitude.text = String(location.altitude)
mgrs.text = String(location.coordinate.latitude)
if (storedLocations.count > 1)
{
let sourceIndex = storedLocations.count - 1
let destinationIndex = storedLocations.count - 2
let c1 = storedLocations[sourceIndex].coordinate
let c2 = storedLocations[destinationIndex].coordinate
var a = [c1, c2]
let polyline = MKPolyline(coordinates: &a, count: a.count)
mapKitView.add(polyline)
print("running")
}
self.mapKitView.showsUserLocation = true
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 5.0
return renderer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

Swift - How Can I Show The MKPolyline For A Tracked Event

My problem is that when a jog has been completed, I can not present the recordedMapView with a full polyline that had tracked the location in an after exercise report.
Currently I am able to persist the data from tracking a run in the first view controller appearing in app, then in the detail view controller I fetch the data and unwrap/assign them to the respective variables, however, I am not sure where I go wrong in the code that is not allowing a polyline to appear. The map region does seem to be getting set properly as the map zooms in and out dynamically to fit the entire journey.
What could be the issue for why the polyline is not being presented and is there a solution to the code that I have provided to correct this problem?
var context : NSManagedObjectContext?
var runTimestamp : NSDate?
var runDuration : NSNumber?
var runDistance : NSNumber?
var runLocations : NSOrderedSet?
var locationTimeStamp : NSDate?
var locationLatitude : NSNumber?
var locationLongitude : NSNumber?
override func viewDidLoad()
{
super.viewDidLoad()
guard let context = context, finishedLocations = fetchLocation ( context ), finishedRun = fetchRun ( context ) else { return }
for location in finishedLocations
{
if let timestamp = location.timestamp, latitude = location.latitude, longitude = location.longitude
{
locationTimeStamp = timestamp
locationLatitude = latitude
locationLongitude = longitude
}
}
for run in finishedRun
{
if let timeStamp = run.timestamp, duration = run.duration, distance = run.distance, locations = run.locations
{
runTimestamp = timeStamp
runDuration = duration
runDistance = distance
runLocations = locations
}
}
updateUI ()
}
func loadMapView()
{
if runLocations!.count > 0
{
recordedMapView.region = mapRegion()
let colorSegments = MulticolorPolylineSegment.colorSegments(forLocations: runLocations!.array as! [Location])
recordedMapView.addOverlays(colorSegments)
}
else
{
let alertController = UIAlertController( title: "Error", message: "No Locations Saved", preferredStyle: .Alert )
let alertAction = UIAlertAction ( title: "Error", style : .Default , handler : nil )
alertController.addAction( alertAction )
presentViewController ( alertController, animated: true, completion: nil )
}
}
func mapRegion() -> MKCoordinateRegion
{
let initialLocation = runLocations!.firstObject as! Location
var minLat = initialLocation.latitude! .doubleValue
var minLng = initialLocation.longitude!.doubleValue
var maxLat = minLat
var maxLng = minLng
let locations = runLocations!.array as! [Location]
for location in locations
{
minLat = min( minLat, location.latitude! .doubleValue )
minLng = min( minLng, location.longitude!.doubleValue )
maxLat = max( maxLat, location.latitude! .doubleValue )
maxLng = max( maxLng, location.longitude!.doubleValue )
}
return MKCoordinateRegion(
center: CLLocationCoordinate2D( latitude : ( (minLat + maxLat)/2 ) , longitude : ( (minLng + maxLng)/2 ) ),
span : MKCoordinateSpan ( latitudeDelta: ( (maxLat - minLat)*1.1) , longitudeDelta: ( (maxLng - minLng)*1.1) )
)
}
func polyline() -> MKPolyline
{
var coordinates = [CLLocationCoordinate2D]()
let locations = runLocations!.array as! [Location]
for location in locations
{
coordinates.append( CLLocationCoordinate2D(latitude: (location.latitude!.doubleValue), longitude: (location.longitude!.doubleValue)) )
}
return MKPolyline(coordinates: &coordinates, count: runLocations!.count)
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer
{
let polyline = overlay as! MulticolorPolylineSegment
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = polyline.color
renderer.lineWidth = 3
return renderer
}
Swift 3.x:
What I see you don't add the polyLine properly into mapView.
Change your polyline function like below;
func polyline()
{
if theJourney != nil && theJourney!.coords != nil{
var coordinates = [CLLocationCoordinate2D]()
let locations = runLocations!.array as! [Location]
for location in locations
{
coordinates.append( CLLocationCoordinate2D(latitude: (location.latitude!.doubleValue), longitude: (location.longitude!.doubleValue)) )
}
}
let polyline = MKPolyline(coordinates: &coordinates, count: runLocations!.count)
mapView.add(polyline)
}
And MKMapView's rendererFor method with code below;
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.green
renderer.lineWidth = 3
return renderer
}
That's should work. Please let me know.

How to draw or make custom route using MKMapView

I am trying to write an app that will show the route of a tram line. I have added the map, but I am having problems trying to find how to add this route to my app.
I have looked at MKOverlayRenderer and I think I have to add an image on top of the map to do this. There are some tutorials but they are outdated.
Can someone help me with this. Thank you
As a beginner you should check the https://www.raywenderlich.com/90971/introduction-mapkit-swift-tutorial.
example
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer! {
if overlay is MKPolyline {
let lineView = MKPolylineRenderer(overlay: overlay)
lineView.strokeColor = UIColor.redColor()
lineView.lineWidth = 1
return lineView
}
return nil
}
func addRoute() {
mapView.deselectAnnotation(selectedAnnotationView.annotation, animated: true)
let track = Track.GetAll()// to get list of coordinates you should write your own way to store
if track.count == 0 {
return
}
var pointsToUse: [CLLocationCoordinate2D] = []
var isTrackChanged = false
for i in 0...track.count-1 {
let x = CLLocationDegrees((track[i].Latitude as NSString).doubleValue)
let y = CLLocationDegrees((track[i].Longitude as NSString).doubleValue)
pointsToUse += [CLLocationCoordinate2DMake(x, y)]
if i > 0 {
if pointsToUse[i-1].latitude != pointsToUse[i].latitude || pointsToUse[i-1].longitude != pointsToUse[i].longitude {
isTrackChanged = true
}
}
}
let myPolyline = MKGeodesicPolyline(coordinates: &pointsToUse, count: track.count)
mapView.addOverlay(myPolyline)
}
//model
class Track{
var latitude =""
var longitude=""
}

Polyline Overlay in Swift

I have my MKMapViewDelegate in place. Also, MapView.delegate = self
let c1 = myCLLocationCoodinate
let c2 = myCLLocationCoodinate2
var a = [c1, c2]
var polyline = MKPolyline(coordinates: &a, count: a.count)
self.MapView.addOverlay(polyline)
With this Delegate Method:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolyline {
var polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.whiteColor()
polylineRenderer.lineWidth = 2
return polylineRenderer
}
return nil
}
I get this: EXC BAD ACCESS Thread 8 on
self.MapView.addOverlay(polyline)
I think issue here is with the line:
var a = [c1, c2]
Here you directly created array without specifying its type.
See below reference code to create Polyline overlay and related delegate method:
let c1 = myCLLocationCoodinate
let c2 = myCLLocationCoodinate2
var points: [CLLocationCoordinate2D]
points = [c1, c2]
var geodesic = MKGeodesicPolyline(coordinates: &points[0], count: 2)
mapView.add(geodesic)
UIView.animate(withDuration: 1.5, animations: { () -> Void in
let span = MKCoordinateSpanMake(20, 20)
let region1 = MKCoordinateRegion(center: c1, span: span)
mapView.setRegion(region1, animated: true)
})
A delegate method to render overlay:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolyline {
var polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.whiteColor()
polylineRenderer.lineWidth = 2
return polylineRenderer
}
return nil
}
It seems that your map view has been deallocated. The polyline construction is OK.
Normally, variables start with lowercase. Have you subclassed the map view and are trying to access the class?
I spent WAAAAAAAAYYYY too much time on this so I thought I would add the solution to a similar issue. I was getting a EXC BAD ACCESS on addOverlay w/ a MKPolygon. Turns out I was just on the wrong thread the whole time. Fixed it with:
var points = [MKMapPoint]()
for var i = 0; i < area.coordinates.count; i+=2 {
let c = CLLocationCoordinate2DMake(area.coordinates[i], area.coordinates[i+1])
points.append(MKMapPointForCoordinate(c))
}
let polygon = MKPolygon(points: &points, count: points.count)
dispatch_async(dispatch_get_main_queue(), {
self.mapView.addOverlay(polygon)
})
let firstlat : string = "12.9166"
let firstlon : string = "77.6101"
let secondlat : string = "12.9610"
let secondLon : string = "77.6387"
let point1 = CLLocationCoordinate2DMake(Double(firstlat)!, Double(firstlon)!)
let point2 = CLLocationCoordinate2DMake(Double(secondlat as String)!, Double(secondLon)!)
let pickAnnotation : MKPointAnnotation = MKPointAnnotation()
pickAnnotation.coordinate = point1
pickAnnotation.title = "pick"
displayMapView.addAnnotation(pickAnnotation)
let dropAnnotation : MKPointAnnotation = MKPointAnnotation()
dropAnnotation.coordinate = point2
dropAnnotation.title = "drop"
displayMapView.addAnnotation(dropAnnotation)
displayMapView.showAnnotations(displayMapView.annotations, animated: true)
var points: [CLLocationCoordinate2D]
points = [point1, point2]
routeLine = MKPolyline(coordinates: &points[0] , count: 2)
displayMapView.add(routeLine)
func showRouteOnMap(_ pickCoordinate: CLLocationCoordinate2D, _ destinationCoordinate: CLLocationCoordinate2D) {
let request = MKDirections.Request()
let sourcePlacemark = MKPlacemark(coordinate: pickCoordinate)
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
request.source = sourceMapItem
let myPlacemark = MKPlacemark(coordinate: destinationCoordinate)
let destinationMapItem = MKMapItem(placemark: myPlacemark)
request.destination = destinationMapItem
request.requestsAlternateRoutes = false
let directions = MKDirections(request: request)
directions.calculate(completionHandler: {(response, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let response = response {
self.showRoute(response)
}
}
})
}
func showRoute(_ response: MKDirections.Response) {
for route in response.routes {
routeMap.addOverlay(route.polyline,
level: MKOverlayLevel.aboveRoads)
self.routeMap.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
// MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor(red: 17.0/255.0, green: 147.0/255.0, blue: 255.0/255.0, alpha: 1)
renderer.lineWidth = 5.0
return renderer
}

Resources