Open directions func not opening apple maps programmatically - ios

I am working on a project right now and am stuck on the final part. I have designed an app with 20 custom annotations to be used by our wedding guests to find nearby restaurants, bars, coffee shops, etc. There are various custom annotation to identify what each location is. It also asks for users current location so they can find things nearby. I would like to be able to click on the annotations and have our friends and family be able to get directions from their current location to the annotation. It seems like the best way to do this is by opening up the apple maps app with my latitude and longitude coordinates in my custom point annotations. My code though does not seem to open that maps app at all. I have added a right call out, I have looked through every since question old and new asking about this and can not find a solution. Any advice will be much appreciated as this is my first real swift app. Below is my current open directions function. Thank you in advance for any help or insight.
func openDirections(_ address :String?) {
self.geocoder.geocodeAddressString(address!, completionHandler: { (placemarks :[CLPlacemark]?, error :NSError?) -> Void in
let placemark = placemarks![0] as CLPlacemark
let destinationPlacemark = MKPlacemark(coordinate: placemark.location!.coordinate, addressDictionary: placemark.addressDictionary as? [String:NSObject])
let mapItem = MKMapItem(placemark: MKPlacemark(coordinate: placemark.location!.coordinate, addressDictionary:nil))
mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving])
let startingMapItem = MKMapItem.forCurrentLocation()
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
let directionsRequest = MKDirectionsRequest()
directionsRequest.transportType = .automobile
directionsRequest.source = startingMapItem
directionsRequest.destination = destinationMapItem
let directions = MKDirections(request: directionsRequest)
directions.calculate(completionHandler: { (response :MKDirectionsResponse?, error :NSError?) -> Void in
let route = response!.routes[0] as MKRoute
if route.steps.count > 0 {
for step in route.steps {
print(step.instructions)
}
}
self.mapView!.add((route.polyline), level: MKOverlayLevel.aboveRoads)
} as! MKDirectionsHandler)
} as! CLGeocodeCompletionHandler)
}

Go ahead and delete everything you wrote and do some research on how to write a new, better, cleaner function. Here is what worked for me and I hope it helps someone else.
func mapView(_ mapView: MKMapView, annotationView view:MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let placemark = MKPlacemark(coordinate: view.annotation!.coordinate, addressDictionary: nil)
let mapItem = MKMapItem(placemark: placemark)
let launchOptions = [MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeTransit]
self.title = title
mapItem.name = title
mapItem.openInMaps(launchOptions: launchOptions)
}

Related

google map url - how do I show address when passing lat and long in url to google maps in iOS?

My problem is that when I am opening google map using the following code.
It's showing lat long in place of address. But i want to show address in google map, or is there any way to pass custom address along with lat long so that google map will show custom address on search bar (coming on top of the image) and show exact location on map with marker.
I had follow the documentation of URLSchemes given by Google.ios-urlscheme
But didn't get any proper solution which fills my conditions.
UIApplication.shared.openURL(URL(string: "comgooglemaps://?q=40.00026321411133,-83.03424072265625&center=40.00026321411133,-83.03424072265625"))
You have to pass address venue name also with the lat-long:
import UIKit
import MapKit
func openMapForPlace() {
let lat1 : NSString = self.venueLat
let lng1 : NSString = self.venueLng
let latitude:CLLocationDegrees = lat1.doubleValue
let longitude:CLLocationDegrees = lng1.doubleValue
let coordinates = CLLocationCoordinate2DMake(latitude, longitude)
let address = [CNPostalAddressStreetKey: address ?? "",
CNPostalAddressCityKey: city ?? "",
CNPostalAddressStateKey: state ?? "",
CNPostalAddressPostalCodeKey: zipCode,
CNPostalAddressISOCountryCodeKey: isoCountryCodeKey ?? ""]
let placemark = MKPlacemark(coordinate: coordinates, addressDictionary: address)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = "\(self.venueName)"
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMaps(launchOptions: launchOptions)
}
In swift code will be below:
import UIKit
import MapKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
openMapForPlace()
}
func openMapForPlace() {
let latitude: CLLocationDegrees = 39.9517958
let longitude: CLLocationDegrees = -75.1611398
let coordinates = CLLocationCoordinate2DMake(latitude, longitude)
let address = [CNPostalAddressStreetKey: "1234 Market St",
CNPostalAddressCityKey: "Philadelphia",
CNPostalAddressStateKey: "Pennsylvania",
CNPostalAddressPostalCodeKey: "19107",
CNPostalAddressISOCountryCodeKey: "USA"]
let placemark = MKPlacemark(coordinate: coordinates, addressDictionary: address)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = "1234 Market St, Philadelphia, Pennsylvania, 19107"
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMaps(launchOptions: launchOptions)
}
}

Remove Polylines at swift

I'm drawing navigation road at Swift. I'm using current location to another location and made a draw. Afterward, I select another location and redraw it. But even if I write mapView.remove(rotapoly) in my code, it doesnt remove it. How can I solve this?
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
cizim = 1;
let capital = view.annotation as! Station
guard let locValue: CLLocationCoordinate2D = locationManager.location?.coordinate else { return }
let neresi = CLLocationCoordinate2D(latitude: capital.latitude, longitude: capital.longitude)
let nerdeyim = CLLocationCoordinate2D(latitude: locValue.latitude, longitude: locValue.longitude)
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: nerdeyim, addressDictionary: nil))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: neresi, addressDictionary: nil))
request.requestsAlternateRoutes = true
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate { [unowned self] response, error in
guard let unwrappedResponse = response else { return }
if (unwrappedResponse.routes.count > 0) {
self.showRoute(response!)
}
}
}
func showRoute(_ response: MKDirectionsResponse) {
mapView.remove(rotapoly)
for route in response.routes {
rotapoly = route.polyline
mapView.add(rotapoly, level: MKOverlayLevel.aboveRoads)
for step in route.steps {
print(step.instructions)
}
}
}
use map view method
self.mapview.removeOverlays(self.mapview.overlays)
this will remove all overlays you have added so you have to do whole process again its like reloading map view
Below is the approach to remove travelled polyline from google map iOS Swift:
var oldPolyLines = [GMSPolyline]() /* Global Array Variable of your Class */
Put below code where you are parsing routes and getting new polyline from direction API.
if self.oldPolyLines.count > 0 {
for polyline in self.oldPolyLines {
polyline.map = nil
}
}
self.oldPolyLines.append(yourNewPolyline)
yourNewPolyLine.map = self.mapView

Calculate distance between my location and a MapKit pin on Swift

I need your help, I'm working on an App where I have some pins (locations) and what I want is to get the distance between each one and my location. My code is the following
let annotation = MKPointAnnotation()
let annotationTwo = MKPointAnnotation()
let saintPaulHospitalBC = MKPointAnnotation()
override func viewDidLoad() {
super.viewDidLoad()
mapita.showsUserLocation = true // Mapita is the name of the MapView.
annotation.coordinate = CLLocationCoordinate2D(latitude: 25.647399800, longitude: -100.334304500)
mapita.addAnnotation(annotation)
annotationTwo.coordinate = CLLocationCoordinate2D(latitude: 25.589339000, longitude: -100.257724800)
mapita.addAnnotation(annotationTwo)
saintPaulHospitalBC.coordinate = CLLocationCoordinate2D(latitude: 49.280524700, longitude: -123.128232600)
mapita.addAnnotation(SaintPaulHospitalBC)
}
When I run the code, the map shows the pins, but what else can I do to start calculating the distance? Thank you!
You're gonna have to convert the coordinates of your annotations to CLLocation types, then get the distance between them. To ignore the height of the coordinates, as they are 2D, just use the latitude and longitude properties of the 2D coordinates, like so:
let loc1 = CLLocation(latitude: coord1.latitude, longitude: coord1.longitude)
However, CLLocation has some other properties such as speed and height, so if you want to factor those in you'll have to give more information. To find the distance between the two locations, do this:
let distance = loc1.distance(from: loc2)
This will give your answer as a double in meters.
Create a helper function to compute the distance between the user location and a given MKPointAnnotation pin:
/// Returns the distance (in meters) from the
/// user's location to the specified point.
private func userDistance(from point: MKPointAnnotation) -> Double? {
guard let userLocation = mapita.userLocation.location else {
return nil // User location unknown!
}
let pointLocation = CLLocation(
latitude: point.coordinate.latitude,
longitude: point.coordinate.longitude
)
return userLocation.distance(from: pointLocation)
}
Finally, to get the user distance to Saint Paul hospital:
if let distance = userDistance(from: saintPaulHospitalBC) {
// Use distance here...
}
Geolocation tracking latency. There is a catch though: the user distance might not always be available at first, since MapKit/CoreLocation geolocation tracking might still be running in the background.
One way around this, is to conform to the MKMapViewDelegate protocol and wait for the mapView(_:didUpdate:) callback before finally computing your distances.
To put it in perspective, you need to first specify what "distance" are you looking for. If you are looking for simple Euclidean Distance then any of the other answers or using distanceFromLocation would work. According to Apple's documentaion on distanceFromLocation
This method measures the distance between the two locations by tracing
a line between them that follows the curvature of the Earth. The
resulting arc is a smooth curve and does not take into account
specific altitude changes between the two locations.
This means, that the distance derived using this method will not be the actual route/transportation distance between two points.
If that is what you are looking for then head over to the answer I linked above, if not then keep reading (but either way, I encourage you to read the whole post :).
If you are looking for "route" distance (drivable, walkable etc.) between your location and the other annotations in the map, it's going to take little more work using MKRoute object. To be more specific you need to first have access to the MKMapItem objects of each of your annotations and then a custom method like below would be able to get the route info between two MapItem objects.
Note - if you don't have MapItems then you can create them just using the coordinates of each of your annotations, like so
ley myCoordinates CLLocationCoordinate2D(latitude: 25.647399800, longitude: -100.334304500)
let myPlacemark = MKPlacemark(coordinate: myCoordinates)
let myMapItem = MKMapItem(placemark: myPlacemark)
Define an MKRoute variable globally in your class (or ViewController class). This var will hold the calculated Route information between two points.
var route: MKRoute!
and then
func getDistanceToDestination(srcMapItem srcmapItem: MKMapItem, destMapItem destmapItem: MKMapItem){
let request = MKDirectionsRequest() //create a direction request object
request.source = srcmapItem //this is the source location mapItem object
request.destination = destmapItem //this is the destination location mapItem object
request.transportType = MKDirectionsTransportType.automobile //define the transportation method
let directions = MKDirections(request: request) //request directions
directions.calculate { (response, error) in
guard let response = response else {
print(error.debugDescription)
return
}
self.route = response.routes[0] //get the routes, could be multiple routes in the routes[] array but usually [0] is the best route
}
}
Usage would be
self.getDistanceToDestination(srcMapItem: yourSourceMapItemObj, destMapItem: yourDestinationMapitemObj)
where yourSourceMapItemObj and yourDestinationMapitemObj are two MapItem objects aka source and destination points.
And then you can access the distance using self.route.distance to get the distance of the first best route returned by MKRoute. There are a whole bunch of other properties for the MKRoute object route which you can use as well to display/calculate other things, and I encourage you to take a look at those too. You can use the function above to also draw a ployLine i.e. a line showing the route between the two locations in the MapView just by adding self.mapView.add(self.route.polyline) in the end of the custom method above and then use the below MKMapViewDelegate function below to render the polyline.
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let linerenderer = MKPolylineRenderer(overlay: self.route.polyline)
linerenderer.strokeColor = .blue
linerenderer.lineWidth = 3.5
return linerenderer
}
And finally, make sure your class (or your class extension) complies to CLLocationManagerDelegate and MKMapViewDelegate protocols and mapview delegate pointed to self (which I assume you already do) in order for everything above to work.
Its easy try my code below.
Don't forget to import CoreLocation or MapKit, hope it helps you
func calculateDistancefrom(sourceLocation: MKMapItem, destinationLocation: MKMapItem, doneSearching: #escaping (_ expectedTravelTim: TimeInterval) -> Void) {
let request: MKDirectionsRequest = MKDirectionsRequest()
request.source = sourceLocation
request.destination = destinationLocation
request.requestsAlternateRoutes = true
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { (directions, error) in
if var routeResponse = directions?.routes {
routeResponse.sort(by: {$0.expectedTravelTime <
$1.expectedTravelTime})
let quickestRouteForSegment: MKRoute = routeResponse[0]
doneSearching(quickestRouteForSegment.distance)
}
}
}
func getDistance(lat: Double, lon: Double, completionHandler: #escaping (_ distance: Int) -> Void) {
let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2DMake(lat, lon)))
guard let currentLocation = self.locationManager?.location else { return }
let sourceItem = MKMapItem(placemark: MKPlacemark(coordinate: currentLocation.coordinate))
self.calculateDistancefrom(sourceLocation: sourceItem, destinationLocation: destinationItem, doneSearching: { distance in
completionHandler(distance)
})
}
//Thereafter get the distance in meters by calling
self.getDistance(lat: yourLat, lon: YourLon) { distance in
}
//you can divide by 1000 to convert to KM... .etc
Using MapKit & Swift 5
Calculate distance between two location location
Sample Function : I have tested in Google Map as well as Apple Map
let startLocation : CLLocation = CLLocation.init(latitude: 23.0952779, longitude: 72.5274129)
let endLocation : CLLocation = CLLocation.init(latitude: 23.0981711, longitude: 72.5294229)
let distance = startLocation.distance(from: endLocation)
self.getDistance(departureDate: Date().adjust(hour: 8, minute: 0, second: 0, day: 0, month: 0), arrivalDate: Date().adjust(hour: 8, minute: 10, second: 0, day: 0, month: 0), startLocation: startLocation, endLocation: endLocation) { (distanceInMeters) in
print("fake distance: \(distance)")
let fakedistanceInMeter = Measurement(value: distance, unit: UnitLength.meters)
let fakedistanceInKM = fakedistanceInMeter.converted(to: UnitLength.kilometers).value
let fakedistanceInMiles = fakedistanceInMeter.converted(to: UnitLength.miles).value
print("fakedistanceInKM :\(fakedistanceInKM)")
print("fakedistanceInMiles :\(fakedistanceInMiles)")
print("actualDistance : \(distanceInMeters)")
let distanceInMeter = Measurement(value: distanceInMeters, unit: UnitLength.meters)
let distanceInKM = distanceInMeter.converted(to: UnitLength.kilometers).value
let distanceInMiles = distanceInMeter.converted(to: UnitLength.miles).value
print("distanceInKM :\(distanceInKM)")
print("distanceInMiles :\(distanceInMiles)")
}
Use of functions
self.getDistance(departureDate: trip.departure.dateTime, arrivalDate: trip.arrival.dateTime, startLocation: startLocation, endLocation: endLocation) { (actualDistance) in
print("actualDistance : \(actualDistance)")
}
I am improved above function and added code here, I hope it will help someone.
func calculateDistancefrom(departureDate: Date, arrivalDate: Date, sourceLocation: MKMapItem, destinationLocation: MKMapItem, doneSearching: #escaping (_ distance: CLLocationDistance) -> Void) {
let request: MKDirections.Request = MKDirections.Request()
request.departureDate = departureDate
request.arrivalDate = arrivalDate
request.source = sourceLocation
request.destination = destinationLocation
request.requestsAlternateRoutes = true
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { (directions, error) in
if var routeResponse = directions?.routes {
routeResponse.sort(by: {$0.expectedTravelTime <
$1.expectedTravelTime})
let quickestRouteForSegment: MKRoute = routeResponse[0]
doneSearching(quickestRouteForSegment.distance)
}
}
}
func getDistance(departureDate: Date, arrivalDate: Date, startLocation : CLLocation, endLocation : CLLocation, completionHandler: #escaping (_ distance: CLLocationDistance) -> Void) {
let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: startLocation.coordinate))
let sourceItem = MKMapItem(placemark: MKPlacemark(coordinate: endLocation.coordinate))
self.calculateDistancefrom(departureDate: departureDate, arrivalDate: arrivalDate, sourceLocation: sourceItem, destinationLocation: destinationItem, doneSearching: { distance in
completionHandler(distance)
})
}

MKMapItem.openInMaps() displays place mark exactly 50% of the time

I've found that running some code to display a location in maps view MKMapItem.openInMaps() only works exactly 50% of the time.
In fact it precisely alternates between the MKPlacemark being displayed and it not being displayed.
For example every 1st, 3rd, 5th, 7th ...nth time the code runs then it displays the place mark, but every 2nd, 4th, 6th, 8th ...mth time it runs, the place mark is not displayed.
This is 100% reproducible running the code posted below.
This seems like its a bug, but if so then I'm surprised its not been reported nor fixed previously. But given the fact the failures are precisely alternating between success and failure leads me to think there's something else going on, hence I'm posting here to see if anybody is familiar with this issue or there is something one is supposed to do which is missing from the code, or there is a workaround:
override func viewDidAppear(_ animated: Bool) {
displayMap()
}
func displayMap()
{
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString("1 Infinite Loop, Cupertino,California") { (placemark: [CLPlacemark]?, error: Error?) -> Void in
if error == nil
{
if let placemark = placemark, placemark.count > 0
{
let location = placemark.first
let latitude = (location?.location?.coordinate.latitude)!
let longitude = (location?.location?.coordinate.longitude)!
let coordinates = CLLocationCoordinate2DMake(latitude, longitude)
let regionDistance:CLLocationDistance = 100000
let regionSpan = MKCoordinateRegionMakeWithDistance(coordinates, regionDistance, regionDistance)
let options = [
MKLaunchOptionsMapCenterKey: NSValue(mkCoordinate: regionSpan.center),
MKLaunchOptionsMapSpanKey: NSValue(mkCoordinateSpan: regionSpan.span)
]
let placemark = MKPlacemark(coordinate: coordinates, addressDictionary: nil)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = "Apple"
mapItem.phoneNumber = "(405) 123-4567"
mapItem.openInMaps(launchOptions: options)
}
}
else
{
assert(false, "Unable to geocode")
}
}
}
This is what the result looks like when the code is run for the first, third, fifth, seventh ... time
And this is the result when the code is run for the second, fourth, sixth, eighth... time
Notice in the failure screenshot that not only is the place mark not displayed on the map but the slide up is also empty.
(Currently observing this on 10.2 but have seen it on other versions too)
Disclaimer
Yes, there seem to be a bug in mapItem.openInMaps, which opens driving directions as an overlay.
As accurately described by #return0:
...I tried your code and the problem lies in the fact that once you have the location centered and showing, if you don't dismiss it and launch the app again it will not show...
More Oddities
The second time around, mapItem.openInMaps does dismiss the driving directions overlay (instead showing the new location)
If the user closes said overlay, Map is no longer confused (*)
(*) A further oddity to that situation is that once that overlay is closed, the location disappears.
Bug? → Workaround!
Force the directions overlay to go away by requesting more than one location. In practice, it means invoking Maps with twice the same location:
MKMapItem.openMaps(with: [mapItem, mapItem], launchOptions: options)
Workaround bonus
Even if the user taps on the anchor and requests driving direction, subsequent invocation to Maps from your application to that or a different location will not leave it hanging. In other words, it works as expected.
Complete Swift 3 Source Code
func displayMap(_ address:String, name:String, tph:String)
{
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemark: [CLPlacemark]?, error: Error?) -> Void in
assert(nil == error, "Unable to geocode \(error)")
if error == nil
{
if let placemark = placemark, placemark.count > 0
{
let location = placemark.first
let latitude = (location?.location?.coordinate.latitude)!
let longitude = (location?.location?.coordinate.longitude)!
let coordinates = CLLocationCoordinate2DMake(latitude, longitude)
let regionDistance:CLLocationDistance = 10000
let regionSpan = MKCoordinateRegionMakeWithDistance(coordinates, regionDistance, regionDistance)
let options = [
MKLaunchOptionsMapCenterKey: NSValue(mkCoordinate: regionSpan.center),
MKLaunchOptionsMapSpanKey: NSValue(mkCoordinateSpan: regionSpan.span)
]
let placemark = MKPlacemark(coordinate: coordinates, addressDictionary: nil)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = name
mapItem.phoneNumber = tph
MKMapItem.openMaps(with: [mapItem, mapItem], launchOptions: options)
} else {
print("Something wrong with \(placemark)")
}
}
}
}
...and invocation
#IBAction func doApple() {
displayMap("1 Infinite Loop, Cupertino, California", name: "Apple", tph: "(408) 996–1010")
}
#IBAction func doMicrosoft() {
displayMap("One Microsoft Way, Redmond, WA", name: "Microsoft", tph: "1-800-MICROSOFT")
}
#IBAction func doIBM() {
displayMap("1 New Orchard Road. Armonk, New York", name: "IBM", tph: "(914) 499-1900")
}
On iOS 10.3.2 this issue still persists for me. However, this workaround is making mapItem not to disappear:
MKMapItem.openMaps(with: [mapItem], launchOptions: options)

iOS 10 NSUserActivity, Location Suggestions - MKMapItem Does not Appear in Maps

I am trying to make place suggestions appear in Apple Maps in the list under "Where do you want to go?". NSUserActivity in iOS 10 now has a mapItem property and I'm setting it with an MKMapItem that I create from an MKPlacemark that I made with geo coordinates and the place name.
The place name does not appear when I go to Maps, as it should. After going through WWDC 2016, session 240 several times, I still cannot find what I'm doing wrong.
The mapItem must be from an MKLocalSearch request that takes your geo coords and place name. The mapItems that you get from the MKLocalSearchResponse are ones that Apple Maps will accept.
let coordinate = CLLocationCoordinate2D(latitude: 38.89005200, longitude: -77.00251600)
var points = [MKMapPointForCoordinate(coordinate)]
let mapRect = MKPolygon(points: &points, count: 1).boundingMapRect
let region = MKCoordinateRegionForMapRect(mapRect)
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "Supreme Court Historical Society"
request.region = region
let localSearch:MKLocalSearch = MKLocalSearch(request: request)
localSearch.start(completionHandler: { (response:MKLocalSearchResponse?, error:Error?) in
if error == nil {
activity.mapItem = response!.mapItems[0]
var userInfo = [String: AnyObject]()
userInfo["placemark"] = NSKeyedArchiver.archivedData(withRootObject: activity.mapItem.placemark)
activity.userInfo = userInfo
activity.contentAttributeSet?.supportsNavigation = true
activity.contentAttributeSet?.supportsPhoneCall = true
}
})

Resources