Swift MapKit Centers on Bottom vs. Top of Pin - swift

I'm using the same function call to display a pin & annotation on my map. When I use this on a current location inside of a reverse geocode call, the pin centers on the base of the pin (obscuring the top of the callout). When I call the same code in any other circumstance (such as when using coordinates passed into the view controller), the map centers on the top of the pin (which is what I want). Code below, image just after this. placesData is an object of a custom class PlacesData, that conforms to both: NSObject, MKAnnotation. Thanks in advance!
func updateMap() {
centerMap(mapLocation: (placeData?.coordinate)!, regionRadius: regionRadius)
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotation(self.placeData!)
mapView.selectAnnotation(self.placeData!, animated: true)
}
Also: if significant, here is the code for my function: mapView(_:viewFor:)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifer = "Marker"
var view: MKPinAnnotationView
if let dequedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifer) as? MKPinAnnotationView {
dequedView.annotation = annotation
view = dequedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifer)
view.canShowCallout = true
view.rightCalloutAccessoryView = UIButton(type: .custom)
}
return view
}

Related

Custom MKAnnotationView want to be tapped again

I'm having problems with my custom MKAnnotationView.
I download the coordinate where to place it from my REST api and I place the pin on map with this code:
private func refreshMapView(andCenterMap: Bool){
DispatchQueue.main.async { [weak self] in
guard let self = self else {
return
}
self.mapView.addAnnotations(self.spotsArray)
if andCenterMap {
self.centerMap(at: self.mapView.userLocation.coordinate)
}
}
}
Ones the pin is placed I zoom automatically the map.
Here the code for the custom annotation creation:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Do not touch the annotation of the user location
if annotation.isKind(of: MKUserLocation.self){
return nil
}
let annoIdentifier = "SPOT"
var annotationView: MKAnnotationView?
if let dequeued = mapView.dequeueReusableAnnotationView(withIdentifier: annoIdentifier) {
annotationView = dequeued
}else{
let av = MKAnnotationView(annotation: annotation, reuseIdentifier: annoIdentifier)
annotationView = av
}
// Changing the image of the pin
annotationView!.annotation = annotation
if let image = UIImage(named: "map_pin") {
annotationView!.image = image
let deltaY = image.size.height/2
annotationView!.centerOffset = CGPoint(x: 0.0, y: -deltaY)
}else{
annotationView!.image = nil
}
return annotationView
}
As you can notice, my custom pin is using this image (#1x, #2x, #3x)
Ones tapped I want to show the "detail view" and I use the annotation as a sender in order to have all the information I need on the next view.
Here the code:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let ann = view.annotation else {
return
}
if ann.isKind(of: MKUserLocation.self){
return
}
self.performSegue(withIdentifier: "showDetailSegue", sender: ann)
}
So I present all the data I need to and I can go back to the "map view".
The problem is:
In the map view, if I want to select the same annotation again, this is not tappable anymore.
I can see it (in the right place) but I can select it again only if I zoom a little bit and I tap "around" the annotation.
Any suggestione how to solve this issue?
Thanks in advance!
Add this end of didSelect
mapView.deselectAnnotation(ann,animated:false)

Swift distinguishing between callout buttons

Hi I'm mostly new to coding and this is my first project that I've worked on. I'm making an app that displays pools which are hiring around your address and wanted to make it so that pressing on the callouts will bring you to a unique ViewController depending on which one you pressed.
From what I've seen so far online it looks like it needs to be done in an extension of the ViewController but was stuck on how to differentiate between callouts (as of right now I have it so that pressing it will show the directions, but I'd rather show more information that just that, which to my limited knowledge would mean displaying another view controller unless anyone else has any other ideas)
extension MapViewController : MKMapViewDelegate
{
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if let annotation = annotation as? Pools {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView =
mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView{
dequeuedView.annotation = annotation
view = dequeuedView
}
else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) as UIView
}
return view
}
return nil
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let location = view.annotation as! Pools
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
location.mapItem().openInMaps(launchOptions: launchOptions)
}
}
Any help would be greatly appreciated!

[Swift]How an annotationview always show callout

I want to show the callout of the annotationview always show, I mean when the mapview is loaded, all the annotations‘ callout is shown.
How can I do this?
At anytime, there can be only one callout on the map. Framework reuses the previous callout when a new is to be presented, and here is showing a single callout view when annotation is added, like so
let annotation = MKPointAnnotation()
annotation.title = "Title for Callout"
mapView.addAnnotation(annotation)
This causes the delegate method viewForAnnotation to be fired:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "AnnotId")
view.canShowCallout = true
return view
}
Framework will add the above returned view and the didAddViews delegate is fired, now show the callout view by selecting the annnotation, like so
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
if let annotation = views.first(where: { $0.reuseIdentifier == "AnnotId" })?.annotation {
mapView.selectAnnotation(annotation, animated: true)
}
}
Add annotation on map: mapView.addAnnotation(myAnnotation)
Select annotation mapView.selectAnnotation(myAnnotation, animated: false)
Add private function private func bringMyAnnotationToFront() with code if let myAnnotation = mapView.annotations.first(where: { $0 is MKPointAnnotation }) { mapview.selectAnnotation(myAnnotation, animated: false) }
Set MKMapViewDelegate and implement two methods with bringMyAnnotationToFront:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView), func mapView(MKMapView, didDeselect: MKAnnotationView)

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

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

Custom MKPointAnnotation isn't responding to user interaction

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
}
}