MapKit Tutorial: Getting Started

Look at this tutorial: https://www.raywenderlich.com/110054/routing-mapkit-core-location

Scroll about halfway down, to ā€œCalculating Routeā€¦ā€ with MapKit. Itā€™s older Swift, but Xcode will help you update it, or check the documentation for MKDirectionsRequest and MKRoute.

Oh sorry, you misunderstood me, I donā€™t wanna make route like in this example, I make route like in your example, and this great, but I want to ā€œfunc mapItem()ā€ works when user tapped not on information button and the my button

put the code from calloutAccessoryControlTapped into your buttonā€™s action:

let location = view.annotation as! Artwork
  let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
  location.mapItem().openInMapsWithLaunchOptions(launchOptions)

your view controller needs to keep track of the annotation view thatā€™s the route destination, so the action uses that as view in the code above

Can you please show example, what i need write in code to create this button?

for example, using the single artwork ā€œKing David Kalakauaā€, at the end of viewDidLoad:

declare artwork as a property of the view controller:

var artwork: Artwork!

In viewDidLoad:

artwork = Artwork(title: "King David Kalakaua", locationName: "Waikiki Gateway Park",
  discipline: "Sculpture", coordinate: CLLocationCoordinate2D(latitude: 21.283921,
    longitude: -157.831661))
mapView.addAnnotation(artwork)

Then connect your button to this IBAction:

  @IBAction func showRoute(_ sender: Any) {
    let location = artwork
    let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
    location!.mapItem().openInMaps(launchOptions: launchOptions)
  }

If I create button like that:
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare))
button.setBackgroundImage(UIImage(named: ā€œcarā€), for: UIControlState())
button.addTarget(self, action: #selector(Artwork.mapItem), for: .touchUpInside)
view?.leftCalloutAccessoryView = button
how can i add it to your navigation method?

if you just want to use the left callout accessory view instead of the right one, you donā€™t need to addTarget; the calloutAccessoryControlTapped method will run when you tap the left button.

do you also have a button on the right side?

I can make it, but how i can add it to your navigation method?

in extension ViewController: MKMapViewDelegateā€™s viewFor annotation, replace these two lines:

view.calloutOffset = CGPoint(x: -5, y: 5)
view.leftCalloutAccessoryView = UIButton(type: .detailDisclosure) as UIView

with your code.

I replaced, it donā€™t work((

I want make button, which add route to the Apple Maps.

leave out the addTarget line, and view isnā€™t optional in viewFor annotation, so donā€™t put the ?:

// 3
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare))
button.setBackgroundImage(UIImage(named: "car"), for: UIControlState())
view.leftCalloutAccessoryView = button

It works. Thank you very much.

1 Like

Hello! Can you help please? How i can add button on the map, which returns camera view to user location?

Hi! Great tutorial! I have an API that returns several locations and I am able to place and show them on the map without any issues. I would like to display the index number (+1) from my locations array of every location in a UIView that I created but canā€™t seem to figure out how to access the indexPath of my array when I select a pin. Can you please help with that? Still fairly new to programming and swift. Thanks

what kind of camera view?

a satellite/hybrid map type?
let launchOptions = [MKLaunchOptionsMapTypeKey: MKMapType.hybrid] in the calloutAccessoryControlTapped method:

or something like Google Maps street view? we have a few Google Maps tutorials, for example https://www.raywenderlich.com/81103/introduction-google-maps-ios-sdk-swift

or thereā€™s MKLaunchOptionsCameraKey, which needs an MKMapCamera object as its value: Apple Developer Documentation

I think you need to store the index in each annotation. For Artwork, I added an index property, and added an index parameter to its init and fromJSON methods:

let index: Int
init(index: Int, title: String, locationName: String, discipline: String, coordinate: CLLocationCoordinate2D) {
  self.index = index
// ...

class func fromJSON(_ json: [JSONValue], index: Int) -> Artwork? {
// ...
return Artwork(index: index, title: title, locationName: locationName!, discipline: discipline!, coordinate: coordinate)

And insert index into the subtitle:

var subtitle: String? {
  return "\(index):  \(locationName)"
}

In ViewController.swift, when I convert JSON into Artworks, at steps 3-5, I keep a counter, and pass its value to fromJSON:

// 3
if let jsonObject = jsonObject as? [String: Any],
// 4
let jsonData = JSONValue.fromObject(jsonObject)?["data"]?.array {
  var counter = 1
  for artworkJSON in jsonData {
    if let artworkJSON = artworkJSON.array,
    // 5
      let artwork = Artwork.fromJSON(artworkJSON, index: counter) {
      artworks.append(artwork)
    }
    counter += 1
  }
}
}

When I tap a pin, it shows the index:

I mean button like that, which return me to the user location. In your example with MapKit.

thatā€™s a Google Maps screen shot?

hereā€™s what I have working in HonoluluArt:

  • Embed view controller in navigation controller to get a navigation bar; add bar button with title ā€œHereā€ to navigation bar
  • Control-drag from bar button into ViewController, and create action centerOnUserLocation(_:)
  • above this action:
    var locationManager = CLLocationManager()
  • in the action:
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
  mapView.showsUserLocation = true
  locationManager.requestLocation()
} else {
  locationManager.requestWhenInUseAuthorization()
}
  • At the bottom of ViewController.swift, outside the ViewController class:
extension ViewController: CLLocationManagerDelegate {

  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let location = locations.first {
      locationManager.stopUpdatingLocation()
      centerMapOnLocation(location)
    }
  }

  func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    print(error.localizedDescription)
  }

}
  • in viewDidLoad(), below mapView.delegate = self
locationManager.delegate = self
  • Follow instructions at the end of the tutorial, in the section about Info.plist item
  • Edit scheme: Options\Core Location: check Allow Location Simulation and select a Default Location.
  • Build and run; drag the map away from simulated user location, then tap the bar button. The first time, an alert will ask permission to use your location. After allowing, youā€™ll have to tap the button a second time, to make the map move. But the next time you move away from the user location, then tap the button, the map will move back to the userā€™s location, with the glowing blue dot. Apple says to ask permission as close as possible to when you need to use their location, but you could use the checkLocationAuthorizationStatus() method, as described in the tutorial, to get the permission out of the way as soon as the app loads.

Thank you very much!