I created a custom annotation view called "PlaceAnnotationView" as shown below:
import Foundation
import MapKit
class PlaceAnnotationView : MKPinAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then in viewForAnnotation I return my custom annotation view as shown:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "PlaceAnnotationView")
if annotationView == nil {
annotationView = PlaceAnnotationView(annotation: annotation, reuseIdentifier: "PlaceAnnotationView")
annotationView?.canShowCallout = true
}
return annotationView
}
Here is the code to add annotations:
private func populateNearByPlaces() {
var region = MKCoordinateRegion()
region.center = CLLocationCoordinate2D(latitude: self.mapView.userLocation.coordinate.latitude, longitude: self.mapView.userLocation.coordinate.longitude)
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = self.selectedCategory
request.region = region
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else {
return
}
for item in response.mapItems {
let annotation = PlaceAnnotation()
annotation.title = item.name
annotation.subtitle = "subtitle"
annotation.mapItem = item
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
}
}
Here is the code for PlaceAnnotationView:
import Foundation
import MapKit
class PlaceAnnotationView : MKPinAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is the code for PlaceAnnotation:
import Foundation
import MapKit
class PlaceAnnotation : MKPointAnnotation {
var mapItem :MKMapItem!
}
But I don't see any of my annotations being displayed on the map. The viewForAnnotation is fired multiple times for each of my annotation but does not display anything on the screen.
Based on the code that you have (so reluctantly) chosen to reveal, it seems the problem is that you never set the annotation's coordinate. But an annotation's coordinate is crucial. This is how the annotation tells where it is supposed to be in the world, and how the annotation view associated with this annotation knows where on the map to appear. Therefore the annotation view associated with this annotation does not know where on the map to appear. Therefore it doesn't appear.
Related
I looked at the posts for this and I still do not receive a custom pin....
Custom Annotation --> this includes setting my image
import UIKit
import MapKit
class CustomPointAnnotation: MKPointAnnotation {
var pinCustomImageName: UIImage!
}
View Controller:
I want to return current location until a button is selected to drop pin
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
//current Location
if !(annotation is CustomPointAnnotation) {
return nil
}
let reuseIdentifier = "pin"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView!.canShowCallout = true
} else {
annotationView!.annotation = annotation
}
if let annotationView = annotationView {
annotationView.image = UIImage(named: "Skyscraper")
annotationView.canShowCallout = true
}
return annotationView
}
func addPin() {
pointAnnotation = CustomPointAnnotation()
pointAnnotation.pinCustomImageName = UIImage(named: "Skyscraper")
pointAnnotation.coordinate = currentLocation.coordinate
pointAnnotation.title = "First Building"
pointAnnotation.subtitle = "Latitude: \(currentLocation.coordinate.latitude), \
(currentLocation.coordinate.longitude)"
mapView.addAnnotation(pointAnnotation)
}
There's nothing seriously wrong with the code. But there can be a couple of things that would cause problems, including:
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.
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:
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
}
}
In viewDidLoad register this annotation view class:
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
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.
I am currently working on creating callouts for annotations I have added to my mapview via MapKit. The annotations work out well but currently callouts aren't being displayed even though I am using the right code to enable them (I believe).
HERE is my viewFor annotation code block.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
if let annotation = annotation as? ClusterAnnotation {
let identifier = "cluster"
return mapView.annotationView(annotation: annotation, reuseIdentifier: identifier)
} else {
let identifier = "pin"
let annotationView = mapView.annotationView(of: MKMarkerAnnotationView.self, annotation: annotation, reuseIdentifier: identifier)
annotationView.isEnabled = true
annotationView.canShowCallout = true
annotationView.accessibilityLabel = "hi"
annotationView.isHidden = false
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView.markerTintColor = UIColor.customBlue()
annotationView.glyphImage = UIImage(named: "person")
return annotationView
}
}
Extension code block for annotationView function.
extension MKMapView {
func annotationView<T: MKAnnotationView>(of type: T.Type, annotation: MKAnnotation?, reuseIdentifier: String) -> T {
guard let annotationView = dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) as? T else {
return type.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
annotationView.annotation = annotation
return annotationView
}
}
The annotation enlarges as I select it, and in the didSelect code block it runs the print statement I run through it. Not exactly sure what is going on that's not allowing the callout to show even though I've literally enabled just about everything.
Please Used This Code.
This code working fine for me.
This Code support Swift4 and Swift5.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Don't want to show a custom image if the annotation is the user's location.
if (annotation is MKUserLocation) {
return nil
} else {
let annotationIdentifier = "AnnotationIdentifier"
let nibName = "MyAnnotationView" //My XIB Name
let viewFromNib = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?.first as! MyAnnotationView // get My XIB
var annotationView: MyAnnotationView?
// if there is a view to be dequeued, use it for the annotation
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) as? MyAnnotationView {
if dequeuedAnnotationView.subviews.isEmpty {
dequeuedAnnotationView.addSubview(viewFromNib)
}
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
} else {
// if no views to dequeue, create an Annotation View
let av = MyAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
av.addSubview(viewFromNib)
annotationView = av // extend scope to be able to return at the end of the func
}
// after we manage to create or dequeue the av, configure it
if let annotationView = annotationView {
annotationView.canShowCallout = true // callout bubble
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView.frame = CGRect(x: 0, y: 0, width: 75, height: 80)
let customView = annotationView.subviews.first as! MyAnnotationView
customView.frame = annotationView.frame
}
return annotationView
}
}
This Code OutPut :
Happy To Help You.
From the source code documentation:
// If YES, a standard callout bubble will be shown when the annotation is selected.
// The annotation must have a title for the callout to be shown.
#property (nonatomic) BOOL canShowCallout;
Without the Title value being set the other items will not show up.
Attempting to show custom point annotations from MapKits local search. When the annotations first load on the map all of them show, but then the overlapping ones disappear. And they only reappear when you zoom in on the area.
Many stack solutions have stated to user view?.displayPriority = .required. But for some reason this line of code doesn't work.
Local Search Function on button press
#IBAction func groceryLocalSearch(_ sender: Any) {
self.localStoresArray.removeAll()
self.LocalMapKit.removeAnnotations(self.LocalMapKit.annotations)
currentLocationBtn.isHidden = false
localRequest.naturalLanguageQuery = "Grocery"
//localRequest.region = LocalMapKit.region
self.localSearchBtn.isEnabled = false
let search = MKLocalSearch(request: localRequest)
search.start(completionHandler: {(response, error) in
if error != nil{
print("Error occured when searching: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("There were no results in the search")
} else {
print("\(response!.mapItems.count) Results found!")
for item in response!.mapItems {
//Add each item to a array to access in table view
self.localStoresArray.append(item)
let stores = MKPointAnnotation()
stores.title = item.name
stores.coordinate = item.placemark.coordinate
self.LocalMapKit.addAnnotation(stores)
}
}
self.LocalMapKit.showAnnotations(self.LocalMapKit.annotations, animated: true)
self.localStoresTableView.reloadData()
})
View for annotation func
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
guard annotation is MKPointAnnotation else { return nil }
let identifier = "Annotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.displayPriority = .required
//annotationView?.canShowCallout = true
} else {
annotationView!.annotation = annotation
}
return annotationView
}
I want it so that when the user does the local search it shows all annotations not matter if they are close to each other without having to zoom in.
Image of current map view:
You apparently haven’t set the delegate for your map view because those annotation views are not MKPinAnnotationView, but rather the default MKMarkerAnnotationView. If you’re going to implement MKMapViewDelegate methods, you have to set the delegate of the map view (either in IB or programmatically).
This disappearing act is because the default MKMarkerAnnotationView is configured to enable clustering but you haven’t registered a MKMapViewDefaultClusterAnnotationViewReuseIdentifier.
So, if you really want pin annotation views and you don’t want clustering, set your map view’s delegate and your method should accomplish what you want.
I’d personally suggest you reduce view controller bloat by moving the configuration of your annotation view into a MKPinAnnotationView subclass:
class CustomAnnotationView: MKPinAnnotationView { // or use `MKMarkerAnnotationView` if you want
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
displayPriority = .required
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then, if you’re targeting iOS 11 and later, in your viewDidLoad, you can register your class, and you don’t have to implement mapView(_:viewFor:) at all:
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
Or, if you want to enjoy clustering properly, you can expand your CustomAnnotationView:
class CustomAnnotationView: MKPinAnnotationView { // or use `MKMarkerAnnotationView` if you want
static let preferredClusteringIdentifier: String? = Bundle.main.bundleIdentifier! + ".CustomAnnotationView"
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
clusteringIdentifier = CustomAnnotationView.preferredClusteringIdentifier
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var annotation: MKAnnotation? {
didSet {
clusteringIdentifier = CustomAnnotationView.preferredClusteringIdentifier
}
}
}
And then register both your annotation view and a cluster annotation view:
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
mapView.register(MKMarkerAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
Then you enjoy clustering with minimal effort.
I'm showing an array of annotations in a map view and need to have the corresponding title and subtitle with each annotation. My current code only gets me the 1st title/subtitle on all of the annotations.
func multiPoint() {
var coordinateArray: [CLLocationCoordinate2D] = []
if receivedArrayOfLats.count == receivedArrayOfLongs.count {
for i in 0 ..< receivedArrayOfLats.count {
let eventLocation = CLLocationCoordinate2DMake(receivedArrayOfLats[i], receivedArrayOfLongs[i])
coordinateArray.append(eventLocation)
}
}
for events in coordinateArray {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: events.latitude, longitude: events.longitude)
annotation.title = receivedAgencyEventSubTypeCode
annotation.subtitle = receivedAgencyId
multiEventMap?.addAnnotation(annotation)
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "annotationView"
var view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if #available(iOS 11.0, *) {
if view == nil {
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
view?.displayPriority = .required
} else {
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
}
view?.annotation = annotation
view?.canShowCallout = true
return view
}
No big surprise. You loop making annotations, and you are saying
annotation.title = receivedAgencyEventSubTypeCode
annotation.subtitle = receivedAgencyId
for every annotation thru the loop. The annotation is different each time. But the values on the right side never change so all the titles and subtitles are the same.
Mapbox provides handy documentation on customising an annotation's image and customising an annotation's view:
https://www.mapbox.com/ios-sdk/examples/annotation-views/
https://www.mapbox.com/ios-sdk/examples/marker-image/
However it doesn't seem possible to combine these ideas and customise an annotation view's image. Basically what I am hoping to do is have an annotation of a photo (which the user picks) which also has a boarder which can be animated when tapped.
Has anyone ran into this limitation as well?
MGLAnnotationView inherits from UIView, so most any technique you might use to add an image to a view will also work here.
A simple way would be to add UIImageView as a subview in a custom MGLAnnotationView subclass:
class CustomImageAnnotationView: MGLAnnotationView {
var imageView: UIImageView!
required init(reuseIdentifier: String?, image: UIImage) {
super.init(reuseIdentifier: reuseIdentifier)
self.imageView = UIImageView(image: image)
self.addSubview(self.imageView)
self.frame = self.imageView.frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
And then use that subclass in mapView:viewForAnnotation::
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
guard annotation is MGLPointAnnotation else {
return nil
}
let imageName = "someImageThatYouHaveAddedToYourAppBundle"
// Use the image name as the reuse identifier for its view.
let reuseIdentifier = imageName
// For better performance, always try to reuse existing annotations.
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
// If there’s no reusable annotation view available, initialize a new one.
if annotationView == nil {
annotationView = CustomImageAnnotationView(reuseIdentifier: reuseIdentifier, image: UIImage(named: imageName)!)
}
return annotationView
}
func mapView( _ mapView: MGLMapView, imageFor annotation: MGLAnnotation ) -> MGLAnnotationImage? {
var annotationImage : MGLAnnotationImage? = nil
annotationImage = MGLAnnotationImage( image:UIImage( named: "imageyouwanttoset", reuseIdentifier: annotation.title ) )
return annotationImage
}