I am making a Swift application that uses MKPointAnnotations, and I recently ran into an issue where I needed to store metadata in my annotations, so I created the custom class below:
class BRETTFAnnotation: MKPointAnnotation {
var tag: Int64
var name: String
init(lat : Double, lon:Double, t : Int64, n: String) {
self.tag = t
self.name = n
super.init()
self.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
}
My MKAnnotationView viewfor MKAnnotation method is shown below:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let newAnnotation = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "reuse")
newAnnotation.canShowCallout = true
let right = self.button(title: "Yes")
right?.addTarget(self, action: #selector(clickedToConfirmNewPoint), for: .touchUpInside)
newAnnotation.rightCalloutAccessoryView = right
let left = self.button(title: "No")
left?.addTarget(self, action: #selector(clickedToCancelNewPoint), for: .touchUpInside)
newAnnotation.leftCalloutAccessoryView = left
return newAnnotation
}
The problem I am running into is when ever I click on my custom BRETTFAnnotation (which I add to my MKMapView) nothing happens. When I was just using the MKPointAnnotation (instead of the BRETTFAnnotation) when I clicked on the map the two buttons on the MKAnnotationView would show. I am trying to get the MKPinAnnotationView to show on touch using my BRETTFAnnotation instead of the MKPointAnnotation.
How can I continue to use my custom annotation and show the callout when the user clicks on the annotation at the same time?
Edit 1: Since it is probably useful the code below is how I make the annotation and add it to the mapView.
let location = gestureRecognizer.location(in: mapView)
let coordinate = mapView.convert(location,toCoordinateFrom: mapView)
print("adding lat,long \(coordinate.latitude),\(coordinate.longitude)")
lastPoint = BRETTFAnnotation(lat: coordinate.latitude, lon: coordinate.longitude, t: 1, n: "")
let annotationView = MKPinAnnotationView(annotation: lastPoint, reuseIdentifier: "reuse")
mapView.addAnnotation(lastPoint)
I fix this problem by making my BRETTFAnnotation a subclass of NSObject and MKAnnotation instead of MKPointAnnotation. Doing this allowed my custom class to receive user interaction and show the callouts.
When you use your own MKAnnoation you can handle your actions in didSelect. Just implement the following code.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let yourAnnotation = view.annotation as? BRETTFAnnotation {
//handle your meta data or/and show UIViews or whatever
}
}
with
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
//getting called when you tap on map or on another annotation (not the selected annotation before)
//hide UIViews or do whatever you want
}
That does work for me:
class ViewController: UIViewController, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
print("didSelect")
if let annoation = view.annotation as? MyAnnoation {
print("metatag \(annoation.metaTag)")
}
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
print("didDeselect")
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
let annotation = MyAnnoation(n: "name", m: "metaTag")
annotation.coordinate = CLLocationCoordinate2D(latitude: 50.0, longitude: 8.0)
mapView.addAnnotation(annotation)
}
}
class MyAnnoation: MKPointAnnotation {
var name: String?
var metaTag: String?
init(n: String, m: String) {
self.name = n
self.metaTag = m
}
}
Related
In my app, I have a mapkit full of annotations and when one is clicked I want a new view to slide up with details on the annotation. Correct me if I'm wrong but I think you need to create a custom annotation in order to implement the didSelect() method.
The problem is that, by default, the custom annotations that pop up don't have the name of what the annotations is, like the default mapkit annotations do, and since I have around 20 annotations at a time, the user has no way of knowing what they are selecting.
Any idea as to how to add a title or label underneath the custom annotation with the name of the annotation? I don't want to make a call out that pops up above the annotation since I'm having the view slide up, filled with the annotations data.
Here is what I have:
extension ViewController : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
annotationView?.animatesDrop = true
annotationView?.canShowCallout = false
} else {
annotationView?.annotation = annotation
}
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
UIView.animate(withDuration: 0.2) {
// the view that will hold the annotations data
self.annotationInfoViewBottomConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
}
As you can see the default annotation has the name of the location underneath, which is what I want but I want it under the "pin" looking custom annotations.
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.
Simple Solution for Name under Marker and color change:
MapKit Delegate Method: (I have used MKMarkerAnnotationView and use markerTintColor to set color)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKMarkerAnnotationView
guard let annotation = annotation as? PlaceAnnotation else {
return annotationView
}
if annotationView == nil {
annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "pin")
} else {
annotationView?.annotation = annotation
}
annotationView?.markerTintColor = annotation.pinTintColor
return annotationView
}
MKAnnotation Custom Class:
class PlaceAnnotation: NSObject, MKAnnotation {
let title: String?
let locationName: String
let discipline: String
let coordinate: CLLocationCoordinate2D
var pinTintColor: UIColor
init(id: String, title: String, locationName: String, discipline: String, coordinate: CLLocationCoordinate2D, pinTintColor: UIColor) {
self.title = title
self.locationName = locationName
self.discipline = discipline
self.coordinate = coordinate
self.pinTintColor = pinTintColor
super.init()
}
}
How to Use:
let place = PlaceAnnotation(title: "My Location", locationName: "", discipline: "", coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lng), pinTintColor: UIColor.systemBlue)
map.addAnnotation(place)
You'll need to set annotationView.enabled and annotationView.canShowCallout to YES
My iOS map view shows an initial annotation. I want the user to be able to click on the map and for the annotation to move to the spot where the user clicked, but to retain the non-coordinate information in the annotation (e.g. title, subtitle). The code below works, but intermittently the title and subtitle are not showing (see animation and that the name "Spot Name" sometimes doesn't show). It's unclear to me why this is occurring. If I add print statements to print spot.name and spot.title after mapView.addAnnotation, the String values are there & remain unchanged in the MKAnnotation conforming class. Also, when I click in the marker, the proper title and subtitle show in the callout, even if they weren't showing in the annotation. Grateful for any advice/corrections. Thanks!
import UIKit
import MapKit
class SpotDetailViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
var spot: Spot! // Spot class conforms to NSObject & MKAnnotation
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
spot = Spot()
spot.coordinate = CLLocationCoordinate2D(latitude: 42.334709, longitude: -71.170061)
spot.name = "Spot Name"
spot.address = "Spot Address, Spot Town, Spot State"
// Set initial region
let regionDistance: CLLocationDistance = 250
let region = MKCoordinateRegionMakeWithDistance(spot.coordinate, regionDistance, regionDistance)
mapView.setRegion(region, animated: true)
mapView.addAnnotation(self.spot)
}
#IBAction func mapViewTapped(_ sender: UITapGestureRecognizer) {
let annotationView = mapView.view(for: mapView.annotations[0])
let touchPoint = sender.location(in: mapView)
guard !(annotationView?.frame.contains(touchPoint))! else {
return
}
let newCoordinate: CLLocationCoordinate2D = mapView.convert(touchPoint, toCoordinateFrom: mapView)
spot.coordinate = newCoordinate
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotation(self.spot)
mapView.setCenter(spot.coordinate, animated: true)
}
}
extension SpotDetailViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifer = "Marker"
var view: MKMarkerAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifer) as? MKMarkerAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifer)
view.canShowCallout = true
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
return view
}
}
I'm developing an OSX app.
I've subclassed a MKAnnotation:
import Foundation
import MapKit
class LocationAnnotation: NSObject, MKAnnotation {
var image: NSImage?
var title: String?
var coordinate: CLLocationCoordinate2D
init(image anImage: NSImage?, title aTitle: String, coordinate aCoordinate: CLLocationCoordinate2D) {
self.image = anImage
self.title = aTitle
self.coordinate = aCoordinate
}
}
In my NSViewController subclass, I've added a MKMapViewDelegate and in Interface Builder I've added a MKMapView and set its delegate to NSViewController.
To find out what's wrong, I'm adding three locations in my ViewDidLoad method, into an array. I'm adding those annotations to my map in ViewDidAppear. I plan to move that to a background thread when I figure out what's wrong.
import Cocoa
import MapKit
class ShowLocationsViewController: NSViewController, MKMapViewDelegate {
#IBOutlet var locationMap: MKMapView!
private var myLocationArray: [LocationAnnotation] = []
private var myRegion: MKCoordinateRegion!
//#MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
myLocationArray = []
myRegion = nil
let locLondon = LocationAnnotation(image: nil, title: "London", coordinate: CLLocationCoordinate2DMake(51.522617, -0.139371))
let locWembley = LocationAnnotation(image: nil, title: "Wembley", coordinate: CLLocationCoordinate2DMake(51.555909, -0.279600))
let locGreenwich = LocationAnnotation(image: nil, title: "Greenwich", coordinate: CLLocationCoordinate2DMake(51.476572, -0.001596))
myLocationArray.append(locLondon)
myLocationArray.append(locWembley)
myLocationArray.append(locGreenwich)
myRegion = MKCoordinateRegion.init(center: locLondon.coordinate, span: MKCoordinateSpanMake(0.20, 0.20)) // 20km span
}
override func viewDidAppear() {
locationMap.addAnnotations(myLocationArray)
locationMap.setRegion(myRegion, animated: true)
}
}
When using delegate's method viewFor annotation: I print out the title of each annotation in a log and all three of them are listed. In another delegate's method didAdd I'm printing out the number of annotations of my map in a log and it prints out three. But on my map, there are no annotations displayed. I tried panning and zooming with no success. Region gets properly set and displayed though.
//#MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
print(locationMap.annotations.count, views.count)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is LocationAnnotation {
let annotationIdentifier = "Location"
var annotationView = locationMap.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) as! MKPinAnnotationView?
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
}
annotationView!.canShowCallout = true
print(annotationView!.annotation!.title ?? "no view")
return annotationView
} else {
print("no annotation")
return nil
}
}
When I try the same code in an iOS app, everything works. I assume there is nothing wrong with my delegate because methods are properly called.
I would appreciate any help you can give me.
I figured out the problem, the way the window is being called.
I created a variable mainWindowController in my AppDelegate, then I subclassed my main NSViewController so that I could hook up AppDelegate to it.
let appDelegate = NSApp.delegate as! AppDelegate
appDelegate.mainWindowController = self
Afterwards I used this code to call my window which was errornous:
let mapViewController = mainWindowController?.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier.init(rawValue: "ShowLocationsID")) as! ShowLocationsViewController
mainWindowController?.contentViewController?.presentViewControllerAsModalWindow(mapViewController)
I changed that in a way that I created a segue in my storyboard and called the following code which worked and my pins did show:
performSegue(withIdentifier: NSStoryboardSegue.Identifier(rawValue: "ShowLocations"), sender: self)
I have tried following different tutorials to make my MKAnnotations have an accessory item that will segue to a different page when clicked; however, I cannot get the item to show. Here is the code I've come up with after reviewing different sources:
extension MapController : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "PlaceAnnotation"
if annotation is PlaceAnnotation {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
} else {
annotationView!.annotation = annotation
}
annotationView!.leftCalloutAccessoryView = nil
annotationView!.rightCalloutAccessoryView = UIButton(type: UIButtonType.detailDisclosure)
return annotationView
}
return nil
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let place = view.annotation as! PlaceAnnotation
let placeName = place.title
print(placeName!)
let placeInfo = place.placeObject
let ac = UIAlertController(title: placeInfo?.title, message: placeInfo?.description, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
Then, to create the annotation, I have this:
for place in places {
let placeCoord = CLLocationCoordinate2D(latitude: CLLocationDegrees(place.latitude)!, longitude: CLLocationDegrees(place.longitude)!)
let annotation = PlaceAnnotation(title: place.title, coordinate: placeCoord, placeObject: place)
mapView.addAnnotation(annotation)
}
And the PlaceAnnotation class is as follows:
class PlaceAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var placeObject: Location?
init(title: String, coordinate: CLLocationCoordinate2D, placeObject: Location) {
self.title = title
self.coordinate = coordinate
self.placeObject = placeObject
}
}
The only thing that displays on the annotation when clicked is the Title, but nothing else. I appreciate any help, thank you very much!
(I am working in Swift 3)
I tried your code and it works perfectly fine for me.
But when I removed say,
mapView.delegate = self
Accessory item did not show up, because the delegate is not called.
I'd like to replace the default MKPointAnnotation logo with a custom one.
So I wrote this code in my MapViewController:
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var myMap: MKMapView!
let locationManager = CLLocationManager()
var monPin:CustomPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!
override func viewDidLoad() {
super.viewDidLoad()
//Mark: - Authorization
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
myMap.delegate = self
myMap.mapType = MKMapType.standard
myMap.showsUserLocation = true
let location = CLLocationCoordinate2D(latitude: 43.2885108, longitude:5.3855545000000124)
let center = location
let region = MKCoordinateRegionMake(center, MKCoordinateSpan(latitudeDelta: 0.025, longitudeDelta: 0.025))
myMap.setRegion(region, animated: true)
monPin = CustomPointAnnotation()
monPin.pinCustomImageName = "mapIcon"
monPin.coordinate = location
monPin.title = "Mon titre"
monPin.subtitle = "mon Sous titre"
pinAnnotationView = MKPinAnnotationView(annotation: monPin, reuseIdentifier: "pin")
myMap.addAnnotation(pinAnnotationView.annotation!)
}
//MARK: - Custom Annotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
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
}
let customPointAnnotation = annotation as! CustomPointAnnotation
annotationView?.image = UIImage(named: customPointAnnotation.pinCustomImageName)
return annotationView
}
Where monPin.pinCustomImageName = "mapIcon" refer to my xassets
But it only display a classic MKPointAnnotation picture (this one: https://i.stack.imgur.com/pD82c.png)
**And I think the problem is that: ** the function mapView is not called anywhere because I tried a simple print("hello") in it and it does not appear in the console and even if I erase all this function code, it does not change anything to my app.
That's why I'm wondering how I could face this problem.
I don't understand 100% of my code because I took it from a kind stack overflow user, I mean I understand all the code except the mapView() part.
I believe Xcode has shown a warning if you have created a new project with default settings. (You should not ignore any of the warnings.)
Instance method 'mapView(mapView:viewForAnnotation:)' nearly matches
optional requirement 'mapView(_:viewFor:)' of protocol
'MKMapViewDelegate'
This line of your code:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
does not implement any of the protocol methods declared in MKMapViewDelegate in Swift 3.
And the quick fix feature (choose the first one) fixes it as:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
If your Xcode does not help you fix if, you may need to make it by yourself.
Anyway, with the fix shown above, the method should be called.