Mapkit - Change annotation image on tapping - iphone

I am new to iphone development. I have added annotations on the map. I am able to catch the tapping event on the annotation in didSelectAnnotationView(). But i want to change the image of the annotation when the user taps on it.

Set the image property, as such.
annView.image = [UIImage imageNamed:#"AnnotationIcon.png"];
EDIT
So, you seems to be using MKPinAnnotationView
which has a pinColor property.
Therefore, you can change it as such
pin.pinColor = MKPinAnnotationColorRed; // green and purple are 2 other colors.

Swift 3:
I'll assume you are using this function already for custom MKAnnotation views
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {...}
and I'll update #djibouti33 answer from Objective-c to Swift 3:
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
view.image = UIImage(named: "unselected_marker")
view.frame = CGRect(x: 0, y: 0, width: 40, height: 40)//keep the new icon size resobable.
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
view.image = UIImage(named: "selected_marker")
view.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
}

If you're using an MKAnnotationView and not MKPinAnnotationView you can just rely on MKMapViewDelegate methods:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
view.image = [UIImage imageNamed:#"selected_image"];
}
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
view.image = [UIImage imageNamed:#"deselected_image"];
}

Related

How do you properly display custom pins in SwiftUI / Mapkit?

I am in the process of building a SwiftUI app that relies on MapKit.
But I am encountering an issue with the rendering of the custom pin marker.
Whenever I add the pin, this is rendered from the center of the image so it does not properly align with the current location.
I have tried adding offsets, changing the origin, but the custom pin image basically goes out of bounds.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: nil)
let size = CGSize(width: 35, height: 40)
UIGraphicsBeginImageContext(size)
UIImage(named: "customPin")?.draw(in: CGRect(origin: CGPoint(x: 0, y: 20), size: size))
//let context = UIGraphicsGetCurrentContext()!
//context.move(to: CGPoint(x:0, y: -200))
// This code didn't affect the rendering
view.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
view.canShowCallout = true
return view
}
Basically what I think is happening is that the offsets that I am applying are moving the image but the bounds of the CGRect remain in the same spot. I do not know how to offset both.
In Apple’s docs: Annotating a Map with Custom Data they provide the following sample code:
private func setupSanFranciscoAnnotationView(for annotation: SanFranciscoAnnotation, on mapView: MKMapView) -> MKAnnotationView {
let reuseIdentifier = NSStringFromClass(SanFranciscoAnnotation.self)
let flagAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier, for: annotation)
flagAnnotationView.canShowCallout = true
// Provide the annotation view's image.
let image = #imageLiteral(resourceName: "flag")
flagAnnotationView.image = image
// Provide the left image icon for the annotation.
flagAnnotationView.leftCalloutAccessoryView = UIImageView(image: #imageLiteral(resourceName: "sf_icon"))
// Offset the flag annotation so that the flag pole rests on the map coordinate.
let offset = CGPoint(x: image.size.width / 2, y: -(image.size.height / 2) )
flagAnnotationView.centerOffset = offset
return flagAnnotationView
}
They’re using the centerOffset to reposition the image/annotation. Have you tried this? They have shifted the image up and also to the right as they want to align a corner to the coordinate, usually I just want center-bottom aligned so supply zero for the x offset.

How to custom the image of MKAnnotation pin

I am trying to change the image that is inside the MKAnnotation without removing the rounded shape.
Here I create a custom class of MKAnnotation:
class MapPin: NSObject, MKAnnotation {
let title: String?
let locationName: String
let coordinate: CLLocationCoordinate2D
init(title: String, locationName: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.locationName = locationName
self.coordinate = coordinate
}
}
Here I create a MapPin and I add it to the mapView
func setPinUsingMKAnnotation() {
let pin1 = MapPin(title: "Here", locationName: "Device Location", coordinate: CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661))
let coordinateRegion = MKCoordinateRegion(center: pin1.coordinate, latitudinalMeters: 800, longitudinalMeters: 800)
mapView.setRegion(coordinateRegion, animated: true)
mapView.addAnnotations([pin1])
}
The first image is what I created, the second image is what I would like it to be.
I even implemented MKMapViewDelegate:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = MKAnnotationView()
annotationView.image = #imageLiteral(resourceName: "heart")
return annotationView
}
This is the result:
The rounded shape disappears.
I saw many tutorials about how to custom a pin, but they only explained how to put an image instead of the pin (like the hearth image above). I would like to know how to change the image (and color) of the pin and keep the rounded shape (see the blue pin image above).
Any hints? Thanks
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:
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)
...
}

MapKit Overlays - Circles

I'm trying to add a circle overlay to the map but it never happens - the annotations are added but thats it
Here is the code sample
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer
let circle = MKCircleRenderer(overlay: overlay)
circle.fillColor = UIColor.black.withAlphaComponent(0.1)
circle.strokeColor = UIColor.red
circle.lineWidth = 9
return circle
}
let circle = MKCircle(center: coordinates, radius: 9000)
mapView.addAnnotation(Loka_Location)
mapView.addOverlay(circle)
You might have missed setting the delegate of the mapView.
mapView.delegate = self
and don't forget to
class ViewController: UIViewController, MKMapViewDelegate {}

How to enable MapKit floor level?

I am trying to show floor level into my apple app. I know in apple map there are some selected place like airport or shopping mall where this floor level can be seen. I need to achieve exactly that. Just need to show the floor level where this is available. As you can see in the picture, in the right hand side of the image there are 5F,4F,3F,2F etc. I have searched the net but left with no clue yet.
You need to use MKOverlay. You would add each floor as an overlay to your MKMapView and show whatever floor the user selects, hide the others.
Here is a sample for making an overlay:
import MapKit
class MapOverlay: NSObject, MKOverlay {
var coordinate: CLLocationCoordinate2D
var boundingMapRect: MKMapRect
override init() {
let location = CLLocationCoordinate2D(latitude: 75.3307, longitude: -152.1929) // change these for the position of your overlay
let mapSize = MKMapSize(width: 240000000, height: 200000000) // change these numbers for the width and height of your image
boundingMapRect = MKMapRect(origin: MKMapPoint(location), size: mapSize)
coordinate = location
super.init()
}
}
class MapOverlayRenderer: MKOverlayRenderer {
let overlayImage: UIImage
init(overlay: MKOverlay, image: UIImage) {
self.overlayImage = image
super.init(overlay: overlay)
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
guard let imageReference = overlayImage.cgImage else { return }
let rect = self.rect(for: overlay.boundingMapRect)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -rect.size.height)
context.draw(imageReference, in: rect)
}
}
Then add it to your map:
let mapOverlay = MapOverlay()
mapView.addOverlay(mapOverlay)
And don't forget the delegate:
mapView.delegate = self
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
return MapOverlayRenderer(overlay: overlay, image: UIImage(named: "overlayImage")!)
}
}

Why my mapView does not display mi circle?

I have a mapView outlet in my iOS project and I added it to my code:
var overlay = MKCircle(center: coords, radius: 100)
self.mapView.add(overlay)
and also, to be sure, i added
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
print("aaa")
let circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.fillColor = UIColor.blue.withAlphaComponent(0.1)
circleRenderer.strokeColor = UIColor.blue
circleRenderer.lineWidth = 1
return circleRenderer
}
But my mapView is displayed but not the circle, why ?