iOS Swift Mapkit Custom Annotation

Custom Annotation for Mapview Swift

There's nothing seriously wrong with the code. But there can be a couple of things that would cause problems, including:

  1. Have you set the delegate (either in IB or programmatically) for the map view? If not, your mapView(_:viewFor:) will never be called. Add breakpoint or debugging print statement to confirm.

  2. Have you confirmed that UIImage(named: "Skyscraper") is successfully retrieving an image? Make sure this is not returning nil.


Note, if only iOS 11 and later, you can simplify this code a bit. Since iOS 11, we no longer need for mapView(_:viewFor:) in simple scenarios like this. I would suggest putting the annotation view configuration code within the annotation view subclass, where it belongs, and avoid cluttering our view controller with a viewFor implementation.

So when you do get the current issue behind you, the recommended process is:

  1. Define classes for your annotation and annotation view:

    class CustomAnnotation: MKPointAnnotation {
    var pinCustomImage: UIImage!
    }

    And

    class CustomAnnotationView: MKAnnotationView {
    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
    super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    canShowCallout = true
    update(for: annotation)
    }

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

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

    private func update(for annotation: MKAnnotation?) {
    image = (annotation as? CustomAnnotation)?.pinCustomImage
    }
    }
  2. In viewDidLoad register this annotation view class:

    mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
  3. Remove mapView(_:viewFor:) implementation.

Now when you add a CustomAnnotation to your map’s list of annotations, it will be rendered correctly.

But I would suggest resolving your current problem first. There’s no point in refining you implementation until these more basic issues are resolved.

iOS Swift MapKit Custom Annotation

I recommend subclassing `MKPointAnnotation.

Pokémon Pin

I have included only the necessary code to display a custom map pin. Think of it as a template.

Outline

  • We will create a point annotation object and assigning a custom image name with the CustomPointAnnotation class.

  • We will subclass the MKPointAnnotation to set image and assign it on the delegate protocol method viewForAnnotation.

  • We will add an annotation view to the map after setting the coordinate of the point annotation with a title and a subtitle.

  • We will implement the viewForAnnotation method which is an MKMapViewDelegate protocol method which gets called for pins to display on the map. viewForAnnotation protocol method is the best place to customise the pin view and assign a custom image to it.

  • We will dequeue and return a reusable annotation for the given identifier and cast the annotation to our custom CustomPointAnnotation class in order to access the image name of the pin.

  • We will create a new image set in Assets.xcassets and place image@3x.png and image@2x.png accordingly.

  • Don't forget plist.

NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription

Sample Image

As always test on a real device.

The swizzle /p>

//1

CustomPointAnnotation.swift

import UIKit
import MapKit

class CustomPointAnnotation: MKPointAnnotation {
var pinCustomImageName:String!
}

//2

ViewController.swift

import UIKit
import MapKit

class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {

@IBOutlet weak var pokemonMap: MKMapView!
let locationManager = CLLocationManager()
var pointAnnotation:CustomPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!

override func viewDidLoad() {
super.viewDidLoad()

//Mark: - Authorization
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()

pokemonMap.delegate = self
pokemonMap.mapType = MKMapType.Standard
pokemonMap.showsUserLocation = true

}

func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = CLLocationCoordinate2D(latitude: 35.689949, longitude: 139.697576)
let center = location
let region = MKCoordinateRegionMake(center, MKCoordinateSpan(latitudeDelta: 0.025, longitudeDelta: 0.025))
pokemonMap.setRegion(region, animated: true)

pointAnnotation = CustomPointAnnotation()
pointAnnotation.pinCustomImageName = "Pokemon Pin"
pointAnnotation.coordinate = location
pointAnnotation.title = "POKéSTOP"
pointAnnotation.subtitle = "Pick up some Poké Balls"

pinAnnotationView = MKPinAnnotationView(annotation: pointAnnotation, reuseIdentifier: "pin")
pokemonMap.addAnnotation(pinAnnotationView.annotation!)
}

func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print(error.localizedDescription)
}

//MARK: - Custom Annotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "pin"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier)

if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}

let customPointAnnotation = annotation as! CustomPointAnnotation
annotationView?.image = UIImage(named: customPointAnnotation.pinCustomImageName)

return annotationView
}
}

Adding a title in front/below a custom annotation mapkit swift 4

You can create a label like so:

let annotationLabel = UILabel(frame: CGRect(x: -40, y: -35, width: 105, height: 30))
annotationLabel.numberOfLines = 3
annotationLabel.textAlignment = .center
annotationLabel.font = UIFont(name: "Rockwell", size: 10)
// you can customize it anyway you want like any other label

Set the text:

annotationLabel.text = annotation.title!!

And then add to annotation view:

annotationView.addSubview(annotationLabel)

Picture of annotation with label

I also added a background and border by doing:

annotationLabel.backgroundColor = UIColor.white
annotationLabel.layer.cornerRadius = 15
annotationLabel.clipsToBounds = true

You can also change where the label is in respect to the annotation by changing the X and Y when creating the label. Negative is to the left and up, positive right and down.

How can I create multiple custom annotations in a map view?

It's been a long time since I've used an MKMapView and custom annotations (it was in Objective-C.)

I seem to remember that in your mapView(_:viewFor:) function you need to test to see if the annotation being passed to you is an MKUserLocation. If it is, return nil:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation is MKUserLocation else {
//This is the user location. Return nil so the system uses the blue dot.
return nil
}
//Your code to create an return custom annotations)
}

Custom Annotations in SwiftUI (MKMapView)

So basically after a bit more searching, I found out the answer.

In order to not change the user's location pin, I have to check the type of annotation and if the type is MKUserLocation I should return nil.

Following that the reason for the crash was that I had to make the Stops structure confirm to MKPointAnnotation and remove or override the coordinate variable then when I am making a list of Stops I can simply define the title, subtitle and coordinates.



Related Topics



Leave a reply



Submit