swiftmapkitmkmapviewmkmapviewdelegate

Change MKMarkerAnnotationView size


How to change MKMarkerAnnotationView size?

I tried to set annotationView.bounds.size = CGSize(width: 50, height: 50) but it does not look like the size has changed. I also tried to print out the size of the view and looks like it is defaulted to 28,28

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
      guard annotation is MKPointAnnotation else { return nil }


      let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: Constant.Indentifier.mapPoint)
      annotationView.canShowCallout = true
      annotationView.animatesWhenAdded = true
      annotationView.glyphImage = UIImage(systemName: "house.fill")
      annotationView.glyphTintColor = .systemBlue
      annotationView.markerTintColor = .white
      print(annotationView.bounds.size) // defaulted to 28,28
      annotationView.bounds.size = CGSize(width: 50, height: 50) // Does not change bubble size
      return annotationView
}

Solution

  • See glyphImage documentation, which talks about the size of the glyph:

    The glyph image is displayed when the marker is in the normal state. Create glyph images as template images so that the glyph tint color can be applied to it. Normally, you set the size of this image to 20 by 20 points on iOS and 40 by 40 points on tvOS. However, if you do not provide a separate selected image in the selectedGlyphImage property, make the size of this image 40 by 40 points on iOS and 60 by 40 points on tvOS instead. MapKit scales images that are larger or smaller than those sizes.

    Bottom line, the MKMarkerAnnotationView has a fixed sizes for its two states, selected and unselected.

    If you want a bigger annotation view, you’ll want to write your own MKAnnotationView. E.g., simply creating a large house image is relatively easy:

    class HouseAnnotationView: MKAnnotationView {
        override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    
            let configuration = UIImage.SymbolConfiguration(pointSize: 50)
            image = UIImage(systemName: "house.fill", withConfiguration: configuration)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    

    By the way, I’d suggest registering this annotation view class, like below, and then removing the mapView(_:viewFor:) method entirely.

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

    Now, the above annotation view only renders a large “house” image. If you want it in a bubble, like MKMarkerAnnotationView does, you’ll have to draw that yourself:

    class HouseAnnotationView: MKAnnotationView {
        override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    
            configureImage()
            configureView()
            configureAnnotationView()
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
    private extension HouseAnnotationView {
        func configureImage() {
            let radius: CGFloat = 25
            let center = CGPoint(x: radius, y: radius)
            let rect = CGRect(origin: .zero, size: CGSize(width: 50, height: 60))
            let angle: CGFloat = .pi / 16
    
            let image = UIGraphicsImageRenderer(bounds: rect).image { _ in
                UIColor.white.setFill()
                let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: .pi / 2 - angle, endAngle: .pi / 2 + angle, clockwise: false)
                path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
                path.close()
                path.fill()
    
                let configuration = UIImage.SymbolConfiguration(pointSize: 24)
                let house = UIImage(systemName: "house.fill", withConfiguration: configuration)!
                    .withTintColor(.blue)
                house.draw(at: CGPoint(x: center.x - house.size.width / 2, y: center.y - house.size.height / 2))
            }
    
            self.image = image
            centerOffset = CGPoint(x: 0, y: -image.size.height / 2) // i.e. bottom center of image is where the point is
        }
    
        func configureView() {
            layer.shadowColor = UIColor.black.cgColor
            layer.shadowRadius = 5
            layer.shadowOffset = CGSize(width: 3, height: 3)
            layer.shadowOpacity = 0.5
        }
    
        func configureAnnotationView() {
            canShowCallout = true
        }
    }
    

    That yields:

    enter image description here

    But even that doesn’t reproduce all of the MKMarkerAnnotationView behaviors. So it all comes down to how much of the MKMarkerAnnotationView behaviors/appearance you need and whether having a larger annotation view is worth all of that effort.