MapView custom pins not displaying details when clicked - ios

I am trying to display the results of an array of places' names on a MapView. Using regular pins is not an issue: they display normally, and when clicked, the detail cell appears with the name of the place.
for var t = 0;t<self.parties2.count;++t {
var placeLatitude:CLLocationDegrees = self.parties2[t].latitude
var placeLongitude:CLLocationDegrees = self.parties2[t].longitude
var placeLocation = CLLocationCoordinate2DMake(placeLatitude, placeLongitude)
var annotation = MKPointAnnotation()
annotation.coordinate = placeLocation
annotation.title = self.parties2[t].name
self.mapView.addAnnotation(annotation)
}
Then I changed the aspect of the pin with this function:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
let identifier = "pin"
var view: MKAnnotationView
view = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
var image = UIImage(named: "symbol.png")
image = self.imageResize(image!, sizeChange: CGSizeMake(20, 20))
view.image = image
return view
}
(I resize the pin image with the imageResize function)
This function succeeds in changing the aspect of the pin: it indeed becomes the desired image. However, clicking these images does not display the details anymore.

OK I just needed to add pinView.canShowCallout = true to my code, I assumed it was true by default since it is not necessary with the original pin.

Related

iOS - Select/Deselect the marker in Apple maps not working

I am selecting multiple markers on maps. Marker selection is working perfectly and maps delegate method
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView){}
is being called.
When callout shown and i am going to deselect the same marker whose callout is showing. Nothing happened.
When callout shown and i click on any other place and then deselect the previous marker it will deselect the marker.
Required:
When callout shown and i am going to deselect the same marker whose callout is showing that need to be deselect.
Used Stack
xCode with swift 4.2
Apple MapKit
MKAnnotationView as Custom marker
class ArtworkView: MKAnnotationView {
var locItem:LocationItem = LocationItem()
override var annotation: MKAnnotation? {
willSet {
guard let artwork = newValue as? Artwork else {return}
locItem = artwork.locItem
if(!artwork.isUser){
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = nil
if let imageName = artwork.imageName {
image = UIImage(named: imageName)
} else {
image = nil
}
detailCalloutAccessoryView = detailLabel
}else{
canShowCallout = false
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = nil
image = UIImage(named: "gifcurrentloc")
detailCalloutAccessoryView = nil
}
}
}
In showCallout()/hideCallout(), use
selectAnnotation:/deselectAnnotation: through the map.
In showCalloutView:/hideCalloutView:, don't call setSelected: on the
MKAnnotationView.
You should not call
this method directly. An MKMapView object calls this method in
response to user interactions with the annotation.
You should try to do it, with method deselectAnnotation using your mapView outlet, like:
mapView?.deselectAnnotation(annotation: yourAnnotation, animated: false)

Button inside default callout view stops responding to touches

So I have a map with tons of annotations. Each annotation has the default callout view attached with a button I created that supposedly takes the user to another view controller. This button works fine at first but for some annotations, it does not register touches unless I zoom in on the annotation or click the annotation again. I am very lost. Here is my code.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = MKAnnotationView()
guard let annotation = annotation as? LocationAnnotation else {return nil}
var identifier = ""
switch annotation.type {
case .nightclub:
identifier = "Nightclub"
case .hookahLounge:
identifier = "Hookah Lounge"
case .bar:
identifier = "Bar"
case .bowling:
identifier = "Bowling Alley"
case .arcade:
identifier = "Arcade"
case .pool:
identifier = "Pool"
}
if let dequedView = mapSF.dequeueReusableAnnotationView(withIdentifier: identifier) {
annotationView = dequedView
} else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
}
annotationView.canShowCallout = true
annotationView.isEnabled = true
let button = UIButton()
button.frame = CGRect(x: 0.0, y: 0.0, width: 30.0, height: 30.0)
let image = UIImage(named: "go")
button.setImage(image, for: .normal)
annotationView.detailCalloutAccessoryView?.backgroundColor = UIColor.darkGray
annotationView.rightCalloutAccessoryView = button
}
And here is the callout accessory function which does work initially but does not work at random times.
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print("true button tapped")
}
Again my issue is that print statement does not execute for various annotations. My console would print that statement out every time I press the button in the callout but for some other times it does not.I have no idea why. Any help would be appreciated as this is one of my last remaining bugs for my app.
This is somewhat old for me now but I DID find a solution. In order to receive touches on the button in my callout, I had to simply add one line annotationView.isUserInteractionEnabled = false. And my image in my callout button was interactive regardless of if the map was zoomed in or not at all times. What a weird scenario but it works swimmingly.

MapKit pin title not visible when placed on overlay?

When I place a pin on top of an overlay, the title of the pin appears to be obscured. When the pin is placed over a point that is not on top of the overlay, the title appears below the pin.
The string lastCoordName is the title of the pin; this string is passed from the previous view controller to the current one (shown below).
Here are some images to describe what I'm talking about...
Above image shows the title right below the pin ("360") when the pin is off the overlay.
But the title goes away when the pin is moved to the correct position on top of the overlay.
The title is still shown when the polyline is drawn horizontally and terminates off the overlay, so the problem isn't that the line is covering the title.
Here is the custom pin class:
class CustomPin : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
init(coordinate: CLLocationCoordinate2D, title: String) {
self.coordinate = coordinate
self.title = title
super.init()
}
}
The relevant part of viewDidLoad()
let pin = CustomPin(coordinate: endPoint, title: lastCoordName)//uses lastCoordName from previous vc (insead of looking up name of last node given coord)
mapView.addAnnotation(pin)
mapView.selectAnnotation(pin, animated: true)
And the mapViewController extension
extension mapViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is SchoolMapOverlay {
return SchoolMapOverlayView(overlay: overlay, overlayImage: #imageLiteral(resourceName: "GBSF1"))
} else if overlay is MKPolyline {
let lineView = MKPolylineRenderer(overlay: overlay)
lineView.strokeColor = UIColor(red:0.2, green:0.48, blue:1.00, alpha:1.0)
lineView.lineWidth = 10.0
return lineView
}
return MKOverlayRenderer()
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
pin.canShowCallout = true
return pin
}
}
Is the overlay covering the pin title? If so, why are the pin and its title not on the same level (above the overlay)?
You can set the layer the overlay is drawn on with
mapView.add(tileOverlay, level: .aboveRoads)
This will draw it above roads but below labels. Problem is, it's below all labels; so depending on what you're showing in your map, you're gonna have to play with labels a bit.
MKOverlayLevel Documentation

Creating a custom map annotation callout with an image, accessing data from an array

I have a parsed a set of data from a JSON file (see example below):
{
"locations": [
{
"id": "0001",
"name": "Helensburgh Tunnels",
"type": "Tunnels, Beach, Views",
"location": "Helensburgh, South Coast",
"image": "Helensburgh-Tunnels.jpg",
"activity": "Walking, photography, tunnelling",
"isVisited": false,
"latitude": -34.178985,
"longitude": 150.992867
}
]
}
I am able to correctly read all of this data into a TableView (everything works correctly), however, I would also like to display all of the locations within the JSON file as annotations on a MapView. So far, everything appears correctly except for a preview image on the left side of the callout box (all the annotation pins appear, when clicked they show a callout with a title and subtitle, but no image).
What do I need to resolve so that I can show this image? I am able to achieve it in other sections of my app, where only one location is shown, however, I cannot seem to figure out how to add images to all the annotation callouts within this view.
This is the code I am using to populate the annotations in my NearMeMapViewController:
import UIKit
import MapKit
import CoreLocation
class NearMeMapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var nearMeMap: MKMapView!
let locationManager = CLLocationManager()
var locations = [Location]()
var location:Location!
override func viewDidLoad() {
super.viewDidLoad()
// parse json
if let locationJson = readLocation(){
if let locationArray = locationJson["locations"] as? [[String:Any]]{
for location in locationArray{
locations.append(Location.init(locationInfo: location))
}
print(locations.count)
}
}
// end parse json
nearMeMap.delegate = self
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.nearMeMap.showsUserLocation = true
// Show annotation
for location in locations {
let annotation = MKPointAnnotation()
annotation.title = location.name
annotation.subtitle = location.type
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
nearMeMap.addAnnotation(annotation)
}
}
// Start test - Custom annotation callout with image
func nearMeMap (_ nearMeMap: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "MyPin"
if annotation.isKind(of: MKUserLocation.self) {
return nil
}
// Reuse the annotation if possible
var annotationView:MKPinAnnotationView? = nearMeMap.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
}
let leftIconView = UIImageView(frame: CGRect.init(x: 0, y: 0, width: 53, height: 53))
leftIconView.image = UIImage(named: location.image)
annotationView?.leftCalloutAccessoryView = leftIconView
return annotationView
}
// End test
I would really appreciate any help! I'm a student and I'm just learning, so sorry if I've used any incorrect terminology or there are noob mistakes.
Swift 3.0
On above code the MKMapViewDelegate should be
func mapView (_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{...}
and not
func nearMeMap (_ nearMeMap: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {...}
Add var currentIndex = 0 as global declaration to identify the current image index. else, you aware to use id from JSON response.
i.e., Global Scope becomes like below code,
let locationManager = CLLocationManager()
var locations = [Location]()
//test variable? implemented to resolve issue with images in custom callout
var location:Location!
var currentIndex = 0
override func viewDidLoad() {...}
Then, the leftIconView will becomes,
let leftIconView = UIImageView(frame: CGRect.init(x: 0, y: 0, width: 53, height: 53))
var images = [String]()
for location in locations{
images.append(location.image)
}
leftIconView.image = UIImage(named: images[currentIndex])
self.currentIndex = self.currentIndex+1 //0,1,2,3...N.
Output:-
when you want to set custom image for annotation view, prefer MKAnnotationView instead of an MKPinAnnotationView. You can set your custom image as annotation by setting it as Image attribute of MKAnnotationView . Check here for more info on MKAnnotationView .
As MKPinAnnotationView is a subclass of MKAnnotationView, it has an image property but it generally (usually when you don't expect it) ignores it and displays its built-in pin image instead.
So it's best to just use a plain MKAnnotationView.
See this answer for more details.You can use MKAnnotationView as explained here.
----- EDIT -----
hide your basic Callout to show your custom image.
annotationView?.canShowCallout = false
Refer this ans to customize CallOut images. Check this link for the detailed tutorial.
Hope it helps. Happy Coding!!

Custom MKAnnotationView location drastically changes when rotate or zoom in/out of map

I've got two custom MKAnnotationViews, which display fine when they are initially added to the MKMapView.
The issue is, when rotating, or zooming out, their drawn locations seem to become more and more off. To reiterate, the closer I zoom in, the more accurate they appear, but when zooming out and rotating they are completely off. Any ideas?
Looks good (initial state):
Looks bad (and wrong):
My viewForAnnotation method is pretty basic, nothing fancy going on here.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? PKDriverAnnotation
{
let identifier = "driver"
var annotationView: PKDriverAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) { // 2
dequeuedView.annotation = annotation
annotationView = dequeuedView as! PKDriverAnnotationView
} else {
// 3
annotationView = PKDriverAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(PKTransactionMapViewController.annotationViewTapped(recognizer:))))
}
self.driverAnnotationView = annotationView
return annotationView
} else if let annotation = annotation as? PKAnnotation {
let identifier = "pin"
var view: MKAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) { // 2
dequeuedView.annotation = annotation
view = dequeuedView
} else {
// 3
view = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.image = UIImage(named: "TransactionAnnotation")
view.canShowCallout = false
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(PKHomeViewController.annotationViewTapped(recognizer:))))
let profilePic = FBSDKProfilePictureView(frame: CGRect(x: 4, y: 4, width: 43, height: 43))
profilePic.center = CGPoint(x: view.bounds.midX, y: profilePic.center.y)
profilePic.profileID = self.transaction!.availableParking.holder.fbid
profilePic.layer.cornerRadius = 21.0
profilePic.clipsToBounds = true
view.addSubview(profilePic)
}
return view
}
return nil
}
Update
I suspected this was somewhat related to anchor points, and I was able to fix the "parking pin annotation views" rotation by view.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0), but had no luck with driver annotation view (the one w/ the car)
Appreciate the help SO!
You should set the centerOffset of your MKAnnotationView. As the documentation says:
By default, the center point of an annotation view is placed at the coordinate point of the associated annotation. You can use this property to reposition the annotation view as needed. This x and y offset values are measured in points. Positive offset values move the annotation view down and to the right, while negative values move it up and to the left.

Resources