MKPointAnnotation custom Image
Asked Answered
M

6

13

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.

Millionaire answered 9/5, 2017 at 18:40 Comment(0)
C
20

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.

Caducous answered 9/5, 2017 at 22:39 Comment(5)
Let us continue this discussion in chat.Caducous
QUESTION! 🤪🤪🤪🤪 you were 100000% right along and this is wonderful! one issue: say you have a number of different MKAnnotationView (example, DogAnnotationView, CatAnnotationView, BudgieAnnotationView). So some annotations have one, some have another. In fact, must we then go back to using the lame delegate call, mapView#viewFor ...? Is that right? It appears there is no way to tell an annotation to use a certain AnnotationView? (In cases where there is more than one annotation view subclass.)Certifiable
@Certifiable Two options: 1. You can have different annotation views and use mapView(_:viewFor:) pattern to identify which to instantiate. 2. You can have a single annotation view that looks at the details of the annotation to figure out what glyphImage or image to use, in which case mapView(_:viewFor:) is not needed.Caducous
See gist.github.com/robertmryan/0bb16a0e5c2863234085c5ce3449550e for example of the latter.Caducous
awesome! so indeed the only way to branch between literally different MKAnnotationView is using mapView#viewFor. Finally, the whole situation is clarified. this answer is likely the single clear and full exposition of the whole situation, on the net. TY!Certifiable
M
1

Finally i did it this way, on my own!

ViewController.swift

    //
    //  ViewController.swift

    //
    //  Created by Alexandros Andreadis on 19/04/2017.
    //  Copyright © 2017 Alexandros Andreadis. All rights reserved.
    //    
    import UIKit
    import MapKit
    import CoreLocation
    import FirebaseDatabase

    class RateViewController: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, CLLocationManagerDelegate, MKMapViewDelegate{
    let pin = UIImage(named: "pin")
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
        {

            if let annotation = annotation as? Locations{
                if let view = mapView.dequeueReusableAnnotationView(withIdentifier: annotation.identifier){
                    return view
                }else{
                    let view = MKAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
                    view.image = pin
                    view.isEnabled = true
                    view.canShowCallout = true
                    //view.leftCalloutAccessoryView = UIImageView(image: pin)
                    return view
                }
            }
            return nil
        }
        override func viewDidLoad() {
            super.viewDidLoad()

            mapView.delegate = self 

            mapView.addAnnotations(locations)



        }


    }

Locations.swift

//
//  Locations.swift

//
//  Created by Alexandros Andreadis on 10/05/2017.
//  Copyright © 2017 Alexandros Andreadis. All rights reserved.
//

import UIKit
import MapKit

class Locations: NSObject, MKAnnotation {
    // required coordinate, title, and the reuse identifier for this annotation
    var identifier = "locations"
    var title: String?
    var coordinate: CLLocationCoordinate2D
    //initializer taking a name, a latitude and longitude to populate the title and coordinate for each instance of this object
    init(name:String,lat:CLLocationDegrees,long:CLLocationDegrees){
        title = name
        coordinate = CLLocationCoordinate2DMake(lat, long)
    }

}
// Creating the list of the places that will be pinned in map
class LocationList: NSObject {
    var Location = [Locations]()
    override init(){
        Location += [Locations(name: "Dio Con Dio", lat: 40.590130, long: 23.036610)]
        Location += [Locations(name: "Paradosiako - Panorama", lat: 40.590102, long:23.036180)]
        Location += [Locations(name: "Veranda",  lat: 40.607740, long: 23.103044)]
        Location += [Locations(name: "Markiz",  lat: 40.634252, long: 22.936276)]
        Location += [Locations(name: "Moi Lounge Bar",  lat: 40.653481, long: 22.994131)]
        Location += [Locations(name: "Boulevard Lounge Bar",  lat: 40.658462, long: 22.983198)]
        Location += [Locations(name: "Ernést Hébrard",  lat: 40.631829, long: 22.941014)]
        Location += [Locations(name: "Tribeca - All Day & Night Bar",  lat: 40.631029, long: 22.942396)]

    }
}
Millionaire answered 9/5, 2017 at 23:22 Comment(10)
If dequeueReusableAnnotationView succeeds, make sure to set the annotation property before you return the annotation view. As it is, your reused annotation views won't work properly.Caducous
@Caducous thank you! Another Question. How can i get pin name and i assign it to a UILabel everytime user touches a pin? For example, if he touches name: Dio Con Dio pin i want this name to be assigned to a UILabel and if he touches the another pin, i want the UILabel take the string value of the last pin touched.Millionaire
You can implement mapView(_:didSelect:) and do it there.Caducous
@Caducous sorry, mistake .. it's okMillionaire
@Caducous Hey Rob. I wrote that code to get the name of pin: annotationTouched = (String(describing: annotation.title)) print (annotationTouched), but it prints Optional("Dio Con Dio") .. And i want only Dio Con Dio word. What to do?Millionaire
You "unwrap" that optional, e.g. let annotationTouched = annotation.title ?? "No title" or guard let annotationTouched = annotation.title else { fatalError("no title") } or let annotationTouched = annotation.title!.Caducous
For more info, see "optionals" section in The Swift Programming Language: developer.apple.com/library/content/documentation/Swift/…Caducous
@Caducous Thank you. I used let annotationTouched = annotation.title! . Last question: override func prepare(for segue: UIStoryboardSegue, sender: Any?) { let DestViewController : RateViewController = segue.destination as! RateViewController DestViewController.placeLabelString = annotationTouched } And i want to get it here to push that value on other view controller, but it doesnt work. We the previous code i had it worked but it pushed Optional("Dio Con Dio") ..Millionaire
It depends on how you got that Optional("Dio Con Dio"). Did you use that String(describing: ) syntax? Don't do that. That's good for debugging purposes, but that's about it. You clearly have some optional that you need to unwrap (perhaps with that ! operator), but it's hard to say on the basis of the information provided. No offense intended here, but you really need to step back and understand optionals, because it's such a fundamental Swift concept (and you're going to get crucified if you post a basic question about it without doing some research). Google "swift optionals tutorial".Caducous
Ok @Caducous I appreciate your help!Millionaire
W
0

SWIFT 5

let pointAnnotation = MKPointAnnotation()

override func viewDidLoad()
{
   mapVw.delegate = self
 pointAnnotation.coordinate = CLLocationCoordinate2D(latitude: yourLatitude, longitude:yourLongitude)
 mapVw.addAnnotation(pointAnnotation)

}

// MARK:- MapView Delegate


func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
    if annotation is MKUserLocation
    {
        return nil;
    }else{
        let pinIdent = "Pin";
        var pinView: MKAnnotationView?
        if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: pinIdent)
        {
            dequeuedView.annotation = annotation;
            pinView = dequeuedView;
        }
        else
        {
            pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: pinIdent);
            pinView?.image = UIImage(named: "yourImage")

        }


        return pinView;
    }
}
Warble answered 3/10, 2019 at 5:32 Comment(1)
This is the worst formatted code I have ever seenArchidiaconal
A
-1
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {

    let identifier = "MyPin"

    if annotation.isKindOfClass(MKUserLocation) {
        return nil
    }

    let detailButton: UIButton = UIButton(type: UIButtonType.DetailDisclosure)

    if let annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) {
        annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "pin")
        annotationView.canShowCallout = true
        annotationView.image = UIImage(named: "custom_pin.png")
        annotationView.rightCalloutAccessoryView = detailButton
    }
    else {
        annotationView.annotation = annotation
    }

    return annotationView
}
Amadus answered 9/5, 2017 at 21:16 Comment(2)
This is Swift 2.3, and generally people are writing Swift 3 code nowadays. Also, your annotationView variable needs to be defined outside of the scope of the if statement. And you want to use the same identifier when creating the annotation view as you did when you create it. And the logic regarding creating the annotation view is backwards (only if dequeue failed should you be instantiating a new annotation view).Caducous
Thanks, It was really helpful. :)Amadus
R
-1

use this method:

optional func mapView(_ mapView: MKMapView, 
           didAdd views: [MKAnnotationView])

works fine for me. (My environment: Xcode 9.4.1 and iOS 11.4.1)

Example:

func mapView(_ mapView: MKMapView,
             didAdd views: [MKAnnotationView])
{
    //views[0] = just added MKAnnotationView
    let pic = UIImage(named: "abc.jpg")
    
    views[0].image = pic
    views[0].layer.cornerRadius = (views[0].frame.size.width) / 2
    views[0].clipsToBounds = true
    
    print("fafafjejowfjpeawijoefaw")
}
Rebane answered 9/8, 2018 at 13:4 Comment(0)
D
-1

I added it in this way:

func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
  let sourceView = views.first
  sourceView?.image = UIImage(named: "Bus")
  let destinationView = views.last
  destinationView?.image = UIImage(named: "Home")
}
Dys answered 13/6, 2019 at 11:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.