Problem with using switch with annotation view customization

Hello,

This is the continuation of https://developer.apple.com/forums/thread/692878?login=true&page=1#692455022 where @Calude31 tried to help me. I've added two buttons for my annotationView. I have 25 annotations and I would like to have seperate button actions for each annotation. Now when I try to customize an action per annotation for rightButton switch only changes colors of the annotations NOT the rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside) - I think because od the return annotationView at the end - I need to change all three annotation parameters (color, icon, button action). Here is my code:

import UIKit
import MapKit

class ViewController: UIViewController, MKMapViewDelegate {

    let initialLocation = CLLocation(latitude: 85.10786543576327, longitude: 11.03851472106171)

    let locationManager = CLLocationManager()

    struct Place {

      let name: String

      let description: String

      let type: String

      let lattitude: CLLocationDegrees

      let longtitude: CLLocationDegrees

        var coordinate: CLLocationCoordinate2D {

            .init(latitude: lattitude, longitude: longtitude)

        }

    }

let location = CLLocation()

    let places = [Place(name: "One", description: "One", type: "one", lattitude: 81.108187, longtitude: 12.075812),

                   Place(name: "Two", description: "Two", typ: "two", lattitude: 81.076187, longtitude: 11.000563),

                   Place(name: "Three", description: "Three", typ: "Three", lattitude: 81.076187, longtitude: 11.000563)]

[...]

    func findPlace(_ miejsca: [Place]) {

        for (i, place) in places.enumerated() {

        let annotations = MKPointAnnotation()

        annotations.title = String(i) + "-" + place.name

        annotations.subtitle = place.description

        annotations.coordinate = CLLocationCoordinate2D(latitude:

          place.lattitude, longitude: place.longtitude)

        mapView.addAnnotation(annotations)

      }

    }

@objc func didClickDetailDisclosure(button: UIButton) {

        guard let vc = storyboard?.instantiateViewController(withIdentifier: "kopuly_vc") as? KopulyController else {

                    return

                }

                present(vc, animated: true)

    }

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

        guard !(annotation is MKUserLocation) else { return nil }

        mapView.delegate = self

        let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))

        let identifier = "identifier"

        annotationView.canShowCallout = true

        guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as? MKMarkerAnnotationView else { return nil }

        let rightButton = UIButton(type: .detailDisclosure) // HERE IS THIS BUTTON, BUT I NEED EACH BUTTON FOR EACH ANNOTATION - THIS WAY IT SHOULD BE ONE FOR ALL OF THEM

        rightButton.tag = annotation.hash

        annotationView.canShowCallout = true

        annotationView.rightCalloutAccessoryView = rightButton
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

let leftButton = UIButton(frame: CGRect(

          origin: CGPoint.zero,

          size: CGSize(width: 25, height: 25)))

        leftButton.setBackgroundImage(#imageLiteral(resourceName: "nav"), for: .normal)

        annotationView.leftCalloutAccessoryView = leftButton

        leftButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

switch annotation.title!! {

        case "One":

            annotationView.markerTintColor = UIColor.gray

            annotationView.glyphImage = UIImage(named: "kopula")

        case "Two":

            annotationView.markerTintColor = UIColor.black

            annotationView.glyphImage = UIImage(named: "two")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)

        case "Three":

            annotationView.markerTintColor = UIColor.red

            annotationView.glyphImage = UIImage(named: "three")
            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)
default:

            annotationView.markerTintColor = UIColor.darkGray

            annotationView.glyphImage = UIImage(named: "place")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

        }

        return annotationView

    }



}

Answered by ForumsContributor in

You do not add a target for One ?

could you show all the selectors you defined ?

what do you get with this code ?

could you also add a print in each case to see if you are getting there.

@Claude31 These is same code that Im running:

import UIKit
import MapKit

class ViewController: UIViewController, MKMapViewDelegate {

    let initialLocation = CLLocation(latitude: 85.10786543576327, longitude: 11.03851472106171)

    let locationManager = CLLocationManager()

    struct Place {

      let name: String

      let description: String

      let type: String

      let lattitude: CLLocationDegrees

      let longtitude: CLLocationDegrees

        var coordinate: CLLocationCoordinate2D {

            .init(latitude: lattitude, longitude: longtitude)

        }

    }

let location = CLLocation()

    let places = [Place(name: "One", description: "One", type: "one", lattitude: 81.108187, longtitude: 12.075812),

                   Place(name: "Two", description: "Two", typ: "two", lattitude: 81.076187, longtitude: 11.000563),

                   Place(name: "Three", description: "Three", typ: "Three", lattitude: 81.076187, longtitude: 11.000563)]

[...]

    func findPlace(_ miejsca: [Place]) {

        for (i, place) in places.enumerated() {

        let annotations = MKPointAnnotation()

        annotations.title = String(i) + "-" + place.name

        annotations.subtitle = place.description

        annotations.coordinate = CLLocationCoordinate2D(latitude:

          place.lattitude, longitude: place.longtitude)

        mapView.addAnnotation(annotations)

      }

    }

@objc func didClickDetailDisclosure(button: UIButton) {

        guard let vc = storyboard?.instantiateViewController(withIdentifier: "second_vc") as? SecondController else {

                    return

                }

                present(vc, animated: true)

    }

@objc func didClickDetailDisclosure2(button: UIButton) {

        guard let vc = storyboard?.instantiateViewController(withIdentifier: "third_vc") as? SecondController else {

                    return

                }

                present(vc, animated: true)

        }

  @objc func didClickDetailDisclosure3(button: UIButton) {

            guard let vc = storyboard?.instantiateViewController(withIdentifier: "fourth_vc") as? SecondController else {

                        return

                    }

                    present(vc, animated: true)

            }

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

        guard !(annotation is MKUserLocation) else { return nil }

        mapView.delegate = self

        let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))

        let identifier = "identifier"

        annotationView.canShowCallout = true

        guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as? MKMarkerAnnotationView else { return nil }

        let rightButton = UIButton(type: .detailDisclosure) // HERE IS THIS BUTTON, BUT I NEED EACH BUTTON FOR EACH ANNOTATION - THIS WAY IT SHOULD BE ONE FOR ALL OF THEM

        rightButton.tag = annotation.hash

        annotationView.canShowCallout = true

        annotationView.rightCalloutAccessoryView = rightButton
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

let leftButton = UIButton(frame: CGRect(

          origin: CGPoint.zero,

          size: CGSize(width: 25, height: 25)))

        leftButton.setBackgroundImage(#imageLiteral(resourceName: "nav"), for: .normal)

        annotationView.leftCalloutAccessoryView = leftButton

        leftButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)

switch annotation.title!! {

        case "0-One":

            annotationView.markerTintColor = UIColor.gray

            annotationView.glyphImage = UIImage(named: "kopula")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

        case "1-Two":

            annotationView.markerTintColor = UIColor.black

            annotationView.glyphImage = UIImage(named: "two")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)

        case "2-Three":

            annotationView.markerTintColor = UIColor.red

            annotationView.glyphImage = UIImage(named: "three")
            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)
default:

            annotationView.markerTintColor = UIColor.darkGray

            annotationView.glyphImage = UIImage(named: "place")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

        }

        return annotationView // Without this one the app shows different VC for each annotation, but without this it does not show markerTintColor and glyphImage.

    }



}
Accepted Answer

// Without this one the app shows different VC

I don't understand how this could even compile; if func does not return a value, compiler should have an error:

Missing return in a function expected to return MKAnnotationView?

Do you have 3 different ViewController in storyboard, withIdentifier: "second_vc", "third_vc" and "fourth_vc", all with the class SecondController ?

I also note that:

        let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))

means annotationView is MKPinAnnotationView, not MKAnnotationView. In addition, MKPinAnnotationView is now deprecated.

Try replacing with:

        let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))

I've copied the code and noted a few points:

  • in let places = […, you have typ instead of type

  • this does not compile:

            mapView.addAnnotation(annotations)

with error message : Value of type '(MKMapView, MKAnnotation) -> MKAnnotationView?' has no member 'addAnnotation'

Here are some changes to test:

import UIKit
import MapKit

class ViewController: UIViewController, MKMapViewDelegate {

    let initialLocation = CLLocation(latitude: 85.10786543576327, longitude: 11.03851472106171)
    let locationManager = CLLocationManager()

    struct Place {
        let name: String
        let description: String
        let type: String
        let lattitude: CLLocationDegrees
        let longtitude: CLLocationDegrees
        var coordinate: CLLocationCoordinate2D {
            .init(latitude: lattitude, longitude: longtitude)
        }
    }

    let location = CLLocation()

    let places = [Place(name: "One", description: "One", type: "one", lattitude: 81.108187, longtitude: 12.075812),
            Place(name: "Two", description: "Two", type: "two", lattitude: 81.076187, longtitude: 11.000563),
            Place(name: "Three", description: "Three", type: "Three", lattitude: 81.076187, longtitude: 11.000563)]  // type: vs typ:

//[...]

    func findPlace(_ miejsca: [Place]) {
        
        for (i, place) in places.enumerated() {
            let annotations = MKPointAnnotation()
            annotations.title = String(i) + "-" + place.name
            annotations.subtitle = place.description
            annotations.coordinate = CLLocationCoordinate2D(latitude: place.lattitude, longitude: place.longtitude)
            mapView.addAnnotation(annotations)  // <<-- WHAT IS THIS
        }
        
    }

    @objc func didClickDetailDisclosure(button: UIButton) {
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "kopuly_vc") as? KopulyController else {
            return
        }
        present(vc, animated: true)
    }

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        
        guard !(annotation is MKUserLocation) else { return nil }
        mapView.delegate = self
        let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))  // <<-- REPLACE WITH MKAnnotationView
        let identifier = "identifier"
        annotationView.canShowCallout = true
        
        guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as? MKMarkerAnnotationView else { return nil }
        
        let rightButton = UIButton(type: .detailDisclosure) // HERE IS THIS BUTTON, BUT I NEED EACH BUTTON FOR EACH ANNOTATION - THIS WAY IT SHOULD BE ONE FOR ALL OF THEM
        rightButton.tag = annotation.hash
        annotationView.canShowCallout = true
        annotationView.rightCalloutAccessoryView = rightButton
        // -->> DON'T DO THIS HERE  rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
        
        let leftButton = UIButton(frame: CGRect(
            origin: CGPoint.zero,
            size: CGSize(width: 25, height: 25)))
        
        leftButton.setBackgroundImage(#imageLiteral(resourceName: "nav"), for: .normal)
        annotationView.leftCalloutAccessoryView = leftButton
        
        leftButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
        switch annotation.title!! {
        case "One":
            print("One")    // <<-- To see if we get there
            annotationView.markerTintColor = UIColor.gray
            annotationView.glyphImage = UIImage(named: "kopula")
            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside) // <<-- NEED THIS ?

        case "Two":
            print("Two")    // <<-- To see if we get there
            annotationView.markerTintColor = UIColor.black
            annotationView.glyphImage = UIImage(named: "two")
            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
            
        case "Three":
            print("Three")    // <<-- To see if we get there
            annotationView.markerTintColor = UIColor.red
            annotationView.glyphImage = UIImage(named: "three")
            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)

        default:
            print("Default")    // <<-- To see if we get there
            annotationView.markerTintColor = UIColor.darkGray
            annotationView.glyphImage = UIImage(named: "place")
            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
            
        }
        
        return annotationView
        
    }

}

Extra answer to force forum to show previous one.

@Claude31 Ok, let me explain:

This one works with couple of VCs, but it does not change the annotation color and image:

     switch annotation.title {

       case "0-One" :

           annotationView.markerTintColor = UIColor.gray
           annotationView.glyphImage = UIImage(named: "one")
           rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

       case "1-Two":
           annotationView.markerTintColor = UIColor.gray
           annotationView.glyphImage = UIImage(named: "two")
           rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
       case "2-Three":
           annotationView.markerTintColor = UIColor.gray
           annotationView.glyphImage = UIImage(named: "three")
           rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)

       case .none:

          rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

       case .some(_):

           rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

       }

This one changes just the color and image in annotation, but does not change the VCs:

switch annotation.title!! {

        case "0-One":

            annotationView.markerTintColor = UIColor.gray

            annotationView.glyphImage = UIImage(named: "kopula")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

        case "1-Two":

            annotationView.markerTintColor = UIColor.black

            annotationView.glyphImage = UIImage(named: "two")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)

        case "2-Three":

            annotationView.markerTintColor = UIColor.red

            annotationView.glyphImage = UIImage(named: "three")
            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)
default:

            annotationView.markerTintColor = UIColor.darkGray

            annotationView.glyphImage = UIImage(named: "place")

            rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

        }

        return annotationView  

    }

  1. Yes, I have different viewcontrollers, I've tested them with normal buttons.
  2. Ok, I've changed that, thanks

Ok, I found a problem - I've added

rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)

before the switch, so program was compiling just this one instead of others in switch. Thank you @Claude31

@Claude31 one more question - how not to display enumerate titles? Now it shows on the map number and title: 0-One ; 1-Twoetc. I just Want to display names, as before. The names are not dynamic, so maybe better option is to use titles withou "tags"?

how not to display enumerate titles?

In fact, as you use explicit names (3One", "Two", … no more need to add the enumerate before.

Change as:

    func findPlace(_ miejsca: [Place]) {
        
        for (i, place) in places.enumerated() {
            let annotations = MKPointAnnotation()
            // REMOVE THIS -->> annotations.title = String(i) + "-" + place.name
            annotations.title = place.name
            annotations.subtitle = place.description
            annotations.coordinate = CLLocationCoordinate2D(latitude: place.lattitude, longitude: place.longtitude)
            mapView.addAnnotation(annotations)  // <<-- WHAT IS THIS
        }
        
    }

And revert to:

switch annotation.title!! {
        case "One":

        case "Two":

        case "Three":
Problem with using switch with annotation view customization
 
 
Q