City Sights: Module 6, Lesson 10 - Handling Events from UIKit Controls

Hi all,

Xcode versIon: Version 16.2
Mac OS: Sequoia 15.3.2
Simulator: iPhone 16 Pro

Sharing this code revision in case others experience the same issue I did (took me an hour trying to web search a solution). After completing the lesson, the user location (blue dot) appeared in the Map but the business landmark pins disappeared. Commenting out // mapView.delegate = context.coordinator (while keeping the rest of the code), resulted in the business pins showing up again, but no callouts appeared when I clicked on a specific pin.

I had Claude.ai troubleshoot and implemented two recommendations see below. The explanation claude gave was:

"The reason your pins aren’t showing when you include mapView.delegate = context.coordinator is because of how you’re implementing the annotation views. When you set a delegate, the system calls your mapView(_:viewFor:) method for each annotation, but your current implementation isn’t properly configuring the annotation views.

The key changes I made:

  1. Used MKMarkerAnnotationView instead of MKAnnotationView:
  • MKMarkerAnnotationView is specifically designed for displaying pins with callouts, making it more appropriate for your use case.
  • It automatically handles the pin appearance and callout display.
  1. Fixed the annotation management in updateUIView:
  • Changed the approach to only remove non-user location annotations.
  • Used addAnnotations instead of showAnnotations for adding the business locations.
  • Called showAnnotations after adding the annotations to ensure all pins (including user location) are visible.
  1. Added proper type casting in the delegate method:
  • Cast the dequeued view to MKMarkerAnnotationView.
  • Added configuration for the marker color.
    "

Changes I made:

  1. func updateUIView(…),

replaced:

// Remove all annotations
    uiView.removeAnnotations(uiView.annotations)
      
// Add the ones based on the business
    uiView.showAnnotations(self.businessLocations, animated: true)

with:

// Remove all annotations except user location

   let existingAnnotations = uiView.annotations.filter { !($0 is MKUserLocation) }
      uiView.removeAnnotations(existingAnnotations)
      
      // Add the business annotations
      uiView.addAnnotations(self.businessLocations)
      
      // Make sure all annotations are visible, including user location
      if !self.businessLocations.isEmpty {
         uiView.showAnnotations(uiView.annotations, animated: true)
      }
  1. func mapView(…)

replaced:

// Check if there's a reusable annotation view
   var annotationView = mapView.dequeueReusableAnnotationView(
            withIdentifier: Constants.annotationReuseId)

with:

 // Create a new annotation view & try to dequeue a reusable annotation view
   var annotationView = mapView.dequeueReusableAnnotationView(
            withIdentifier: Constants.annotationReuseId
         ) as? MKMarkerAnnotationView

and replaced:

// Create a new annotation view
   annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: Constants.annotationReuseId)

with:

// Create a new marker annotation view if none available for reuse
   annotationView = MKMarkerAnnotationView(
               annotation: annotation,
               reuseIdentifier: Constants
                  .annotationReuseId)

Hope this helps save you some troubleshooting time.

//
//  BusinessMap.swift
//  CitySights App
//
//  Created by Prash on 3/23/25.
//

import SwiftUI
import MapKit // this is a UIKit control (not a SwiftUI)

struct BusinessMap: UIViewRepresentable {

   @EnvironmentObject var model: ContentModel
   
   var businessLocations:[MKAnnotation] {
   
      var annotations = [MKAnnotation]()
      
      // Create set of annotations from our list of businesses
      
      for business in model.restaurants + model.sights {
         
         // if biz has lat and long, create MKPointAnnotation for it. Below, if business.coordinates.latitude is not nil, it gets assigned to lat; same for long
         
         if let lat = business.coordinates?.latitude, let long = business.coordinates?.longitude {
            
            // Create new annotation
            
            let a = MKPointAnnotation()
            
            a.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
            a.title = business.name ?? ""
            
            annotations.append(a)

         } // if let
      } // for
      
       return annotations
       
   } // var businessLocations

   func makeUIView(context: Context) -> MKMapView {
      
      let mapView = MKMapView()
      // lets system handle creation of new coordinator or use existing coordinator
      
      mapView.delegate = context.coordinator
      
      // Show user locaiton the map
      mapView.showsUserLocation = true

      // rotates when user changes directions
      mapView.userTrackingMode = .followWithHeading
      
      return mapView
   }

   func updateUIView(_ uiView: MKMapView, context: Context) {
      // going to called several times when Map is called
      
      // CLAUDE: Remove all annotations except user location
      let existingAnnotations = uiView.annotations.filter { !($0 is MKUserLocation) }
      uiView.removeAnnotations(existingAnnotations)
      
      // Add the business annotations
      uiView.addAnnotations(self.businessLocations)
      
      // Make sure all annotations are visible, including user location
      if !self.businessLocations.isEmpty {
         uiView.showAnnotations(uiView.annotations, animated: true)
      }
      // END OF CLAUDE
      
      /*
      // Remove all annotations
      uiView.removeAnnotations(uiView.annotations)
      
      
      // Add the ones based on the business
      uiView.showAnnotations(self.businessLocations, animated: true)
      */
   }

   static func dismantleUIView(_ uiView: MKMapView, coordinator: Coordinator) {
      
      // cleans up UIView when it's no longer needed
      uiView.removeAnnotations(uiView.annotations)
   }
   
   // MARK: COORDINATOR CLASS
   
   func makeCoordinator() -> Coordinator {

      // return new Coordinator instance
      return Coordinator(map: self)
   }
   
  
   // remember we're declaring this inside Struct
   class Coordinator: NSObject, MKMapViewDelegate {
   
      var map: BusinessMap
      
      init(map: BusinessMap) {
         self.map = map
      }
      
      func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
         
         // If annotation is user location represented by blue dot on map, return nil
         
         if annotation is MKUserLocation {
            return nil
         }
        
         // CLAUDE: Create a new annotation view & try to dequeue a reusable annotation view
         var annotationView = mapView.dequeueReusableAnnotationView(
            withIdentifier: Constants.annotationReuseId
         ) as? MKMarkerAnnotationView
         
         /* Chris's code
        // Check if there's a reusable annotation view
         var annotationView = mapView.dequeueReusableAnnotationView(
            withIdentifier: Constants.annotationReuseId)
        */
            
         if annotationView == nil {
         
            // CLAUDE: Create a new marker annotation view if none available for reuse
            annotationView = MKMarkerAnnotationView(
               annotation: annotation,
               reuseIdentifier: Constants
                  .annotationReuseId)
            // END CLAUDE

            /*
               // Create a new annotation view
               annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: Constants.annotationReuseId)
            */
            
            // allows you to display external info in call out bubble
            annotationView?.canShowCallout = true
            
            annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
                     
            annotationView?.markerTintColor = .red
            
         } else {
            // we have a resuable annotationView
          
            annotationView?.annotation = annotation
         }
         // return it
         return annotationView

      } // func
   } // class
    
} // struct