Change Mkmarkerannotationview Size

Change MKMarkerAnnotationView size

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:

Sample Image

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.

MKMarkerAnnotationView has a truncated image

I have found the solution! I have just to set the contentMode to scaleAspectFit and to change the bounds to the size of the image I need (40x40px).

Here's the code displaying the full MKMarkerAnnotationView.

let pinAnnotation = MKMarkerAnnotationView()
pinAnnotation.markerTintColor = UIColor.red
pinAnnotation.glyphText = "1"
pinAnnotation.animatesWhenAdded = false
pinAnnotation.glyphTintColor = UIColor.white
pinAnnotation.titleVisibility = .hidden
pinAnnotation.subtitleVisibility = .hidden
pinAnnotation.contentMode = .scaleAspectFit
pinAnnotation.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)

UIGraphicsBeginImageContextWithOptions(pinAnnotation.bounds.size, false, 0.0)
pinAnnotation.drawHierarchy(in: CGRect(x:0,
y:0,
width:pinAnnotation.bounds.width,
height:pinAnnotation.bounds.height),
afterScreenUpdates: true)
let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

This page Demystifying iOS Layout has helped me a lot to find this solution.

How to custom the image of MKAnnotation pin

If you want that rounded border, you can render it yourself, or easier, subclass MKMarkerAnnotationView rather than MKAnnotationView:

class CustomAnnotationView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
didSet { configure(for: annotation) }
}

override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

glyphImage = ...
markerTintColor = ...

configure(for: annotation)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(for annotation: MKAnnotation?) {
displayPriority = .required

// if doing clustering, also add
// clusteringIdentifier = ...
}
}

That way, not only do you get the circular border, but you get all of the marker annotation view behaviors (shows the title of the annotation view below the marker, if you select on the marker annotation view, it becomes larger, etc.). There’s a lot of marker annotation view behaviors that you probably don’t want to have to write from scratch if you don’t have to. By subclassing MKMarkerAnnotationView instead of the vanilla MKAnnotationView, you get all those behaviors for free.

For example, you could:

class CustomAnnotationView: MKMarkerAnnotationView {
static let glyphImage: UIImage = {
let rect = CGRect(origin: .zero, size: CGSize(width: 40, height: 40))
return UIGraphicsImageRenderer(bounds: rect).image { _ in
let radius: CGFloat = 11
let offset: CGFloat = 7
let insetY: CGFloat = 5
let center = CGPoint(x: rect.midX, y: rect.maxY - radius - insetY)
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi, clockwise: true)
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY + insetY), controlPoint: CGPoint(x: rect.midX - radius, y: center.y - offset))
path.addQuadCurve(to: CGPoint(x: rect.midX + radius, y: center.y), controlPoint: CGPoint(x: rect.midX + radius, y: center.y - offset))
path.close()
UIColor.white.setFill()
path.fill()
}
}()

override var annotation: MKAnnotation? {
didSet { configure(for: annotation) }
}

override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

glyphImage = Self.glyphImage
markerTintColor = #colorLiteral(red: 0.005868499167, green: 0.5166643262, blue: 0.9889912009, alpha: 1)

configure(for: annotation)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(for annotation: MKAnnotation?) {
displayPriority = .required

// if doing clustering, also add
// clusteringIdentifier = ...
}
}

That yields:

Sample Image

Obviously, when you set glyphImage, set it to whatever image you want. The old SF Symbols doesn't have that “drop” image (though iOS 14 has drop.fill). But supply whatever 40 × 40 pt image view you want. I'm rendering it myself, but you can use whatever appropriately sized image from your asset catalog (or from the system symbols) that you want.


As an aside, since iOS 11, you wouldn't generally wouldn't implement mapView(_:viewFor:) at all, unless absolutely necessary (which it isn't in this case). For example, you can get rid of your viewFor method and just register your custom annotation view in viewDidLoad:

override func viewDidLoad() {
super.viewDidLoad()

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

...
}

Select MKMarkerAnnotationView with custom callout after adding annotation

I just encountered a similar problem because I had a UITapGestureRecognizer of my own competing with MKMapView's selection gesture recognizer. I created and selected the annotation on the tap event, but because both gesture recognizers were firing, and theirs was firing last, it was deselecting immediately.

My solution was to implement UIGestureRecognizerDelegate and become the delegate of my gesture recognizer, and implement the following:

public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}

Changing the image and color of a mapView annotation

You can follow this tutorial and download the source code, you can see that it will tackle how to change the marker colors and images.

https://www.raywenderlich.com/7738344-mapkit-tutorial-getting-started

Changing annontation image on iOS depending on object type

I had a problem like that and the only way I solved it was to remove all data points from the MKMapView and then readd them with the only difference that in the func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) i was checking whitch title the MKAnnotationView had.
To achieve that I created an object class that extended MKAnnotationView witch also had a title variable so the list of my annotations was that classes objects.
When the user changes the image of the MKAnnotation you change the title variable in the choice and you first do

self.sceneView.removeAnnotations(annotations)

and after you re-add the annotation points. The delegate function that will get called will be like that

func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
for view in views{
if((view.annotation is MKUserLocation) == false){
// Resize image
let dt:DataPoint = view.annotation as! DataPoint //Data point is my class
var pinImage:UIImage!
if(dt.title = "example"){
pinImage = exampleImage
size = CGSize(width: 60, height: 60) //whetever size
}else{
pinImage = anotherImage
}
UIGraphicsBeginImageContext(size)
pinImage?.draw(in: CGRect(x:0, y:0, width: size.width, height: size.height))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
view.image = resizedImage
}
}
}


Related Topics



Leave a reply



Submit