MapKit Tutorial: Getting Started | raywenderlich.com

Learn to use the powerful MapKit framework to build an interactive map, displaying location details and launching Maps for driving directions.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/7738344-mapkit-tutorial-getting-started

Hey there! thank you for your Tutorial! Iā€™ts awesome and I learned a lot. I now try to figure out, if a user would Press on the ā€˜ā€˜infoā€™ā€™ Button on the Annotation, instead to bring the user to the Maps App, would id be Possible to show them a Picture in Fullscreen of the Art? I couldnā€™t find any Tutorials for this on the internet.

Hi There, You could definitely do that yes. Youā€™d need:

  • A full screen image of the artwork
  • A UIViewController that contains a UIImageView

When the info button is tapped, you could create an instance of your view controller, set itā€™s image viewā€™s image to be your full size one, and then present it modally. Thereā€™s some handy documentation about this in Apples view controller programming guide: View Controller Programming Guide for iOS: Presenting a View Controller

Hello, I followed your steps, but just after adding the pictures to the annotations you lose the name of the place under the annotation. ok we get it back in the CalloutAccessoryView but is it possible to keep also the title under the annotation, useful when the map is displayed and to see the name of the different places or place to click on all the annotations before finding the right one. thank you

Hi @fabrice,
The ArtworkView subclasses MKAnnotationView which is a very simple annotation and only has an image property that you can set, as we did in the tutorial. But you can always add a UILabel to the ArtworkView. If you only set the image, the annotation viewā€™s frame will match the size of the image. If you want a label too, there will be some extra work to do setting the right frame for the custom annotation view and laying out the image and label, but it can be done.

Thatā€™s why the marker annotation and pin annotation view classes that MapKit provides are so useful. All that work is done for you.

Good Luck!

Hi - thanks for the great tutorial, itā€™s actually the only method Iā€™ve found that has allowed me to reliably change the colour of map points. I did notice that at the beginning of the tutorial you use dequeueReusableAnnotationView but at the end you donā€™t use it. Should it be possible to use it and get the same outcome ?
I am building an app that has 1400 points on the map and it is suffering from performance issues with zooming and browsing the map.
Thanks

James

Hi @jtapping,
When you make a custom annotation view, and use

mapView.register( ArtworkMarkerView.self, 
  forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier) 

Youā€™re doing the same thing as using the dequeueReusableAnnotationView(withIdentifier:) method, MapKit handles the view reuse automatically for you.

ah ok thanks for explaining

Hi, This is a little off topic but I have followed this tutorial in order to display 1400 points on a map but I have come across memory management issues when I remove the annotations and then add them again. The memory isnā€™t released so I am assuming this is a memory leak.
Can someone point me in the right direction in regards to releasing memory in relation to this mapkit example please? I have looked into ARC and weak / unowned self etc but I havenā€™t managed to put it into practice relating to the tutorial. thanks!

Hi @jtapping, are you open to sharing some code? Thatā€™d make it easier to see what might be the issue. Other than that have you tried using Xcode instruments? Thereā€™s a Leaks one that is just for this purpose.

Hi yeah of course I can share code, although itā€™s literally the code from the tutorial slighty modified for a bike sharing app. I have debugged in Xcode and this shows that there are strong references to the annotationviews and to a favouriteButton that I added.

Thanks

import Foundation
import MapKit

class StationMarkerView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
willSet {

        guard let station = newValue as? Station else {
            return
        }
        canShowCallout = true
        
        let mapsButton = FavouriteButton()
        let  bool = station.is_favourite
        let image = bool! ? "star.fill": "star"
        
        mapsButton.setBackgroundImage(UIImage(systemName: image), for: .normal)
        
        rightCalloutAccessoryView = mapsButton
        
        markerTintColor = station.markerTintColor
        glyphImage = station.glyphImage
        
    }
}

}

import UIKit

class FavouriteButton: UIButton {

override init(frame: CGRect) {
    super.init(frame: frame)
    initButton()
}
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    initButton()
}

func initButton() {
    
    frame.origin = CGPoint.zero
    frame.size = CGSize(width: 32, height: 32)
    tintColor = UIColor.orange
    adjustsImageWhenHighlighted = false
    addTarget(self, action: #selector(FavouriteButton.activateButton), for: .touchUpInside)
    
}

@objc func activateButton(){
    
    if currentBackgroundImage == UIImage(systemName: "star") {

        setBackgroundImage(UIImage(systemName: "star.fill"), for: .normal)
        
    } else {
        setBackgroundImage(UIImage(systemName: "star"), for: .normal)
        
    }
    
}

}

Strong references are not bad by themselves, itā€™s a retain cycle you watch out for. Where two objects have a strong reference to each other that prevent them from being released. I canā€™t see anything that would cause a retain cycle in the code above, but MapKit does advise against repeatedly adding and removing annotations for performance reasons.

Also, one issue might be that your favourite button is not being reused, every time an annotation is set to an annotation view, you create a new favourite button. Even if the annotation view already has one. That might be something to investigate.

Sorry I couldnā€™t find a quick answer, but hope that helps.

ok interesting thanks a lot.

hi - I managed to find a quick and dirty solution to my problem. Removing the annotations and then changing the maptype releases all memory relating to the mapView.

So to refresh my map I do ā€¦

mapView.removeAnnotations(mapView.annotations)
        
mapView.mapType = MKMapType.satellite
        
velibManager.fetchVelib()
        
mapView.mapType = MKMapType.standard

Awful isnā€™t it ? :slight_smile:

I ran our app on my iPhone, and my iPhone has the location of Honolulu, and the hour date of Honolulu, how can I my iPhone right???

Oh dear! You can set your phoneā€™s location and timezone in the Settings app. Look under General/Date & Time. Hope that helps.

Great tutorial - thank you so much! Iā€™m trying to extend the functionality a little bit by adding a ā€œmenuā€ button/icon over the map to bring up some other functions. However, the button doesnā€™t seem to display over the map. Any ideas on how I can achieve this?

Thanks again,

Tim

Hello Andrew,
thank you and Audrey so much for this tutorial. The last time I was actively involved in programming was almost 35 years ago and last year, due to Corona, I started looking into building an app. I have seen and read many tutorials and with yours I learned extremely much, thank you!
I built an app and based on the MapKit tutorial I added a ā€œMapViewerā€, integrated new icons and created my own GEOJSON file with new information from ā€œmy surroundingsā€.

Now, unfortunately, I have a small problem on which I get stuck. In my GeoJSON file I have also URLs (to the websites of the respective location) and also phone numbers to the locations included. Of course I would like to use these as well. In addition to the right callout button, I have also defined a left callout button with a ā€œSafariā€ icon. When pressing this button, the corresponding homepage should be opened in Safari (maybe later also in a ā€œWebViewerā€ in my app). But somehow I donā€™t get further to connect the function with this button. After many attempts, I have now returned to the source of the code where everything still worked without errors.

In Artwork.swift:

class Artwork: NSObject, MKAnnotation {
let title: String?
let locationName: String?
let discipline: String?
let link: URL?
let coordinate: CLLocationCoordinate2D

furtherā€¦

init(
title: String?,
locationName: String?,
discipline: String?,
link: URL?,
coordinate: CLLocationCoordinate2D
) {
self.title = title
self.locationName = locationName
self.discipline = discipline
self.link = link
self.coordinate = coordinate

super.init()
}



// 3
title = properties["title"] as? String
locationName = properties["location"] as? String
discipline = properties["discipline"] as? String
link = NSURL(string: "link")! as URL
coordinate = point.coordinate
super.init()

In ArtworkViews.swift

  canShowCallout = true
  calloutOffset = CGPoint(x: -5, y: 5)
  rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
  leftCalloutAccessoryView = UIButton(type: .detailDisclosure)

furtherā€¦

class ArtworkView: MKAnnotationView {
override var annotation: MKAnnotation? {
willSet {
  guard let artwork = newValue as? Artwork else {
    return
  }

  canShowCallout = true
  calloutOffset = CGPoint(x: -5, y: 5)
  let mapsButton = UIButton(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 48, height: 48)))
  mapsButton.setBackgroundImage(#imageLiteral(resourceName: "Map"), for: .normal)
  rightCalloutAccessoryView = mapsButton

  let webButton = UIButton(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 48, height: 48)))
  webButton.setBackgroundImage(#imageLiteral(resourceName: "Safari"), for: .normal)
  leftCalloutAccessoryView = webButton

In this version the left and the right button have the same function (open Map). Unfortunately I failed miserably when trying to connect the URL to the left button and have it open in Safari.

In Germany they say: You have scissors in your head! That means, it is probably worst, if you have the scissors in the head, this internalized censorship, which puts shackles on you, even before you write.

Greetings from Germany
Michael

Hi Michael, what I donā€™t see in your code is the delegate method thatā€™s called when the button is tapped? Thatā€™s the mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) method.

Thatā€™s called when any accessory button is tapped. So youā€™ll need a way to tell which control was tapped (perhaps setting the tag property to a specific number?) Also you can use SFSafariViewController if you want to open the URLs within your app.