Search code examples
iosswiftmapkitmkpointannotation

MKPointAnnotation custom Image


This is my code and i want to add a custom pin (.png file) instead of the red pin. I tried to use MKPinAnnotationView and MKAnnotationView but i couldn't add coordinates, subtitles and title. I'm new to iOS development.

override func viewDidLoad() {
    super.viewDidLoad()
    // Handle the text field’s user input through delegate callbacks.
    commentTextField.delegate = self
    
    coreLocationManager.delegate = self
    //desired accuracy is the best accuracy, very accurate data for the location
    coreLocationManager.desiredAccuracy = kCLLocationAccuracyBest
    //request authorization from the user when user using my app
    coreLocationManager.requestWhenInUseAuthorization()
    
    coreLocationManager.startUpdatingLocation()
    
    dbRef = FIRDatabase.database().reference()
    
    struct Location {
        let title: String
        let latitude: Double
        let longitude: Double
        let subtitle: String
    }
    // Locations array
    let locations = [
        Location(title: "Dio Con Dio",    latitude: 40.590130, longitude: 23.036610,subtitle: "cafe"),
        Location(title: "Paradosiako - Panorama", latitude: 40.590102, longitude: 23.036180,subtitle: "cafe"),
        Location(title: "Veranda",     latitude: 40.607740, longitude: 23.103044,subtitle: "cafe")
    ]
    
    for location in locations {
        let annotation = MKPointAnnotation()
        
        annotation.title = location.title
        annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
        annotation.subtitle = location.subtitle
        
        map.addAnnotation(annotation)
    }
}

I am using Swift 3.


Solution

  • FWIW, nowadays I would generally not implement mapView(_:viewFor:) at all. I would just register my annotation view in viewDidLoad:

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

    And I would have an annotation view subclass, which configures the annotation view as appropriate:

    class DogAnnotationView: MKAnnotationView {
        override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
            configure(for: annotation)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            configure(for: annotation)
        }
    
        override var annotation: MKAnnotation? { didSet { configure(for: annotation) } }
    
        private func configure(for annotation: MKAnnotation?) {
            image = UIImage(systemName: "dog.fill")
            …
        }
    }
    

    Yielding:

    enter image description here

    Or we might use MKMarkerAnnotationView:

    class DogAnnotationView: MKMarkerAnnotationView {
        override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
            configure(for: annotation)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            configure(for: annotation)
        }
    
        override var annotation: MKAnnotation? { didSet { configure(for: annotation) } }
    
        private func configure(for annotation: MKAnnotation?) {
            glyphImage = UIImage(systemName: "dog.fill")
            …
        }
    }
    

    Yielding:

    enter image description here

    Either way, you can do whatever configuration of the annotation view you want (e.g., I generally use clustering by setting clusteringIdentifier and register another annotation view class for MKMapViewDefaultClusterAnnotationViewReuseIdentifier), but that is not relevant here. The idea is to just register the annotation view class and put its configuration in that annotation view class rather than contributing to view controller bloat with MKMapViewDelegate method mapView(_:viewFor:).

    For posterity’s sake, my original answer with mapView(_:viewFor:) is below.


    You need to specify your view controller as the delegate for the map view (either in IB or programmatically in viewDidLoad and then (a) specify that you're conforming to the MKMapViewDelegate protocol; and (b) implement mapView(_:viewFor:):

    extension ViewController: MKMapViewDelegate {
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            let identifier = "MyPin"
            
            if annotation is MKUserLocation {
                return nil
            }
            
            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
            
            if annotationView == nil {
                annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
                annotationView?.canShowCallout = true
                annotationView?.image = UIImage(named: "custom_pin.png")
                
                // if you want a disclosure button, you'd might do something like:
                //
                // let detailButton = UIButton(type: .detailDisclosure)
                // annotationView?.rightCalloutAccessoryView = detailButton
            } else {
                annotationView?.annotation = annotation
            }
            
            return annotationView
        }
    }
    

    For more information, see the Location and Maps Programming Guide: Creating Annotation Views from Your Delegate Object. The code snippets are in Objective-C, but it describes the basic process.