So I am creating an app to track a user's running routes. I am to show a polyline that represents where a user has ran, between a start annotation that is dropped when user begins and a end annotation that is dropped when user clicks a finish button. Seeing as how someone can run pretty much anywhere, is there a way to draw a polyline that is Not limited to just main roads and streets? For example if someone runs somewhere that does not have main roads/streets, how would I show the polyline going through that area on the map? (See Image) Or is that not even possible in swift?
In addition, is it possible to fix these gaps?
Here is my code for rendering the polyline:
func drawPolyline() {
guard let startingCoordinate = self.startingCoordinate, let endingCoordinate = self.endingCoordinate else { return }
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: startingCoordinate))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: endingCoordinate))
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate { [weak self] (response, error) in
guard let self = self else { return }
guard error == nil else { return }
guard response != nil else { return }
if let route = response?.routes.first {
self.mapView.addOverlay(route.polyline)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, edgePadding: UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15), animated: true)
}
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay
renderer.strokeColor = .systemBlue
renderer.lineWidth = 3
return renderer
}
// code to get coordinate for annotations
guard let coordinate = locationManager.location?.coordinate else { return }
let annotation = MapAnnotation(title: title, coordinate: coordinate)
mapView.addAnnotation(annotation)
You will get an updated location every time it changes, and you can either build up an array of locations, or store them in a database for later retrieval.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
// the last location presented here is the current position of the runner
self.locations.append(locations.last)
// store this in a database, or as a class variable
}
You can set the desired accuracy when you initialise your location manager
locationManager.desiredAccuracy = kCLLocationAccuracyBest
and then when you're creating the map, create a line showing the runner's route
let polyline = MKPolyline(coordinates: self.locations, count: self.locations.count)
self.mapView.addOverlay(polyline)
Here's an example of the output - looks like the finish is in the middle of the run, but that's because I was heading back home
Related
I tried to add a polyline from the user's location to destination by using the following code, I am sure that I have conformed to the delegate and ensure that the user is in authorizedAlway mode in the authorization status. However, the console generated this error message saying "[UserSession] maps short session requested but session sharing is not enabled" I can not find anything related on how to solve this error.
func generatePolyLine(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
//start from the user's current location to find the ride
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { response, error in
if let error = error {
print("Error calculating direction request \(error)")
}
guard let response = response else { return }
self.route = response.routes.first
guard let polyLine = self.route?.polyline else { return }
self.mapView.addOverlay(polyLine, level: .aboveRoads)
}
}
Did you add the delegate method to specify the renderer? Something like:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.lineWidth = 3.0
renderer.alpha = 0.5
renderer.strokeColor = UIColor.blue
return renderer
}
if let circle = overlay as? MKCircle {
let renderer = MKCircleRenderer(circle: circle)
renderer.lineWidth = 3.0
renderer.alpha = 0.5
renderer.strokeColor = UIColor.blue
return renderer
}
return MKCircleRenderer()
}
I have been following Sean Allens tutorial on MapKit and Directions. I used the code to fit it to my SwiftUI project.
Everything works fine, the screen zoom to fit start and destination on the screen, and calculated the rout correctly.
However, it does to actually display the route.
The main Code in my ViewModel (note: .delegate = self is actually in a function, I wrote it at top level for easier understanding how I am setting the Delegat)
func checkForLocationServices() {
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager!.delegate = self
}
}
//MARK: Directions
func getDirections() {
guard let location = locationManager?.location?.coordinate else {
return
}
let request = createDirectionRequest(from: location)
let directions = MKDirections(request: request)
directions.calculate { calcResponse, error in
guard let response = calcResponse else { return }
for route in response.routes {
print("Route \(route.name) is \(route.distance) long")
self.mapView.addOverlay(route.polyline, level: .aboveRoads)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
}
func createDirectionRequest(from coordinate: CLLocationCoordinate2D) -> MKDirections.Request {
let startLocation = MKPlacemark(coordinate: coordinate)
let destinationLocation = MKPlacemark(coordinate: bbcLocation.coordinate)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startLocation)
request.destination = MKMapItem(placemark: destinationLocation)
request.transportType = .automobile
request.requestsAlternateRoutes = true
return request
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay as! MKPolyline)
renderer.strokeColor = .blue
renderer.lineWidth = 5.0
print(renderer.strokeStart)
print(renderer.strokeEnd)
return renderer
}
The Delegate function rendererFor is never called, despite me setting the Delegate as seen above. I also checked via Breakpoints that it is actually set, as well as downloaded Seans source code, which set the Delegate the same way I did.
My VM conforms to the MKMapViewDelegate Protocol.
Another user had the same problem, but since he got no response, I thought I might try I someone can help me.
Core-question: Why is my delegate method not called, despite me setting it correctly, and knowing (through console logs) that my route is found, calculated, and in fact zoomed in on?
If you need more code, feel free to ask.
Thanks in advance.
*Seans Project
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func getDirections() {
guard let location = locationManager.location?.coordinate else {
//TODO: Inform user we don't have their current location
return
}
let request = createDirectionsRequest(from: location)
let directions = MKDirections(request: request)
resetMapView(withNew: directions)
directions.calculate { [unowned self] (response, error) in
//TODO: Handle error if needed
guard let response = response else { return } //TODO: Show response not available in an alert
for route in response.routes {
self.mapView.addOverlay(route.polyline)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
}
func createDirectionsRequest(from coordinate: CLLocationCoordinate2D) -> MKDirections.Request {
let destinationCoordinate = getCenterLocation(for: mapView).coordinate
let startingLocation = MKPlacemark(coordinate: coordinate)
let destination = MKPlacemark(coordinate: destinationCoordinate)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startingLocation)
request.destination = MKMapItem(placemark: destination)
request.transportType = .automobile
request.requestsAlternateRoutes = true
return request
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay as! MKPolyline)
renderer.strokeColor = .blue
return renderer
}
``
Hi how can I transform my address into coordinates (longitude and latitude) using the geocoder in swift ? I want to be able to pin the location of an address which is saved in a variable.
let address = GlobalVariable.eventClicked.address
#IBOutlet var EventsMapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.EventsMapView.delegate = self
}
I'm using this code to get the coordinates but i'm not able to save it outside this completion blocks, I'm new in swift.
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemarks, err) in
guard err == nil,
let allPlacemarks = placemarks,
let place = allPlacemarks.first,
let loc = place.location else {
return
}
print("log \(loc.coordinate)")
}
So my question is how can i save the coordinates and then add a pin on the map ? Thanks
I calculate the route and I want to add a pin annotation at the end...
declare location, send request, declare directions and reset my map view with new directions...
guard let location = locationManager.location?.coordinate else { return }
let request = createDirectionsRequest(from: location)
let directions = MKDirections(request: request)
resetMapView(withNew: directions)
after I calculate the direction and add annotation...
directions.calculate { [unowned self] (response, error) in
guard let response = response else { return }
for route in response.routes {
self.mapView.add(route.polyline, level: MKOverlayLevel.aboveRoads)
self.mapView.setCenter(route.polyline.coordinate, animated: true)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
self.mapView.setRegion(MKCoordinateRegionMakeWithDistance(route.polyline.coordinate, route.distance*0.75, route.distance*0.75), animated: true)
let routeAnnotation = MKPointAnnotation()
routeAnnotation.coordinate = MKCoordinateForMapPoint(route.polyline.points()[route.polyline.pointCount/2])
self.mapView.addAnnotation(routeAnnotation)
}
}
and this is the result... the annotation is not in the right position of my route... where i'm wrong?
UPDATE
In my case I solved the problem...
I select my destination with a pointer positioned on center of the view... move the map and return me the center coordinates with a function that i write for calculate it:
func getCenterLocation(for mapView: MKMapView) -> CLLocation {
let latidude = mapView.centerCoordinate.latitude
let longitude = mapView.centerCoordinate.longitude
return CLLocation(latitude: latidude, longitude: longitude)
}
I simply call this func for determine my pin coordinates that are the same of the center of the view (pointer) and consequently are the same the destination point...
routeAnnotation.coordinate = self.getCenterLocation(for: self.mapView).coordinate
and this is the result
more solutions are accepted...
I have an issue with my GMSMarker, it seems that it does not stick on user location. I want to stick the marker to user location itself, check my code below.
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
UpdateService.instance.updateUserLocation(withCoordinate: position.target)
UpdateService.instance.updateContractorLocation(withCoordinate: position.target)
}
func loadContractorAnnotation() {
DispatchQueue.main.async {
DataService.instance.REF_CONTRACTOR.observeSingleEvent(of: .value, with: { (snapshot) in
if let contractorSnapshot = snapshot.children.allObjects as? [DataSnapshot] {
for contractor in contractorSnapshot {
if contractor.hasChild("coordinate") {
if contractor.childSnapshot(forPath: "contractorIsAvailable").value as? Bool == true {
if let contractorCoordinateDict = contractor.value as? Dictionary<String, Any> {
let coordinateArray = contractorCoordinateDict["coordinate"] as! NSArray
let contractorCoordinate = CLLocationCoordinate2D(latitude: coordinateArray[0] as! CLLocationDegrees , longitude: coordinateArray[1] as! CLLocationDegrees)
CATransaction.begin()
CAAnimation.init().duration = 0.5
self.contractorMarker.position = contractorCoordinate
self.contractorMarker.iconView = self.markerImageView(image: UIImage(named: "contractor-marker")!, size: CGSize(width: 30, height: 30))
self.contractorMarker.groundAnchor = CGPoint(x: 0.5, y: 0.5)
CATransaction.commit()
self.contractorMarker.map = self.mapView
}
}
}
}
}
})
}
}
// ViewDidLoad()
DataService.instance.REF_CONTRACTOR.observe(.value, with: {(snapshot) in
self.loadContractorAnnotation()
})
And it looks like whenever I drag my map it seems that it will follow the icon centered in my google map instead it will stick only on user location when i drag the google map. You can check it on this video that I upload.
My bad, It seems that I put everything on google map,
I moved the lines of code below from didChange position function.
UpdateService.instance.updateUserLocation(withCoordinate: position.target)
UpdateService.instance.updateContractorLocation(withCoordinate: position.target)
into this block of code.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])