Button inside default callout view stops responding to touches - ios

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.

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)

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.

Swift - Changing Annotation Image Not Working

I have followed other stack posts and tutorials in changing my annotations image to a custom one, but it does not seem to work. No errors or runtime errors appear, it's just that the annotation image does not change.
Just by the way I set a break point on the line annotationView!.image = UIImage(named: "RaceCarMan2png.png") and it shows that the line is being called, but yet nothing happens. I would really appreciate your help. Thanks.
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let identifier = "MyCustomAnnotation"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
annotationView!.image = UIImage(named: "RaceCarMan2png.png")
} else {
annotationView!.annotation = annotation
}
configureDetailView(annotationView!)
return annotationView
}
func configureDetailView(annotationView: MKAnnotationView) {
annotationView.detailCalloutAccessoryView = UIImageView(image: UIImage(named: "url.jpg"))
}
The issue is that you're using MKPinAnnotationView. If you use MKAnnotationView, you will see your image.
In the past (e.g. iOS 8), setting the image of MKPinAnnotationView seemed to work fine, but in iOS 9 and later, it uses a pin, regardless (which is not entirely unreasonable behavior for a class called MKPinAnnotationView; lol).
Using MKAnnotationView avoids this problem.
Call that before return
if let annotationView = annotationView {
// Configure your annotation view here
annotationView.image = UIImage(named: "RaceCarMan2png.png")
}
return annotationView
and check with breakpoint if it is ok inside if

Why calloutAccessoryControlTapped is called for tap on annotation view also?

I have a map on my view controller and I don't know why but the delegate calloutAccessoryControlTapped is also called when I just tap on annotation view, not only when I tap on detail closure. So why this behavior?
import UIKit
import MapKit
extension MapVC: MKMapViewDelegate, CLLocationManagerDelegate
{
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
{
...
}
}
As per Apple developer docs:
Accessory views contain custom content and are positioned on either
side of the annotation title text. If a view you specify is a
descendant of the UIControl class, the map view calls this method as a
convenience whenever the user taps your view. You can use this method
to respond to taps and perform any actions associated with that
control. For example, if your control displayed additional information
about the annotation, you could use this method to present a modal
panel with that information.
If your custom accessory views are not descendants of the UIControl
class, the map view does not call this method.
So is your accessory view inherited from UIControl?
Reference:
https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKMapViewDelegate_Protocol/#//apple_ref/occ/intfm/MKMapViewDelegate/mapView:annotationView:calloutAccessoryControlTapped:
Massimo Polimeni seems to be correct. There appears to be a problem with the rightCalloutAccessoryView, but not with the leftCalloutAccessoryView.
The code below (with a leftCalloutAccessoryView) works as expected. If you tap the left accessory, it prints "left accessory selected". If you tap the callout title, it doesn't print anything.
If you use a rightCalloutAccessoryView (commented out, below) and tap the right accessory, it prints "right accessory selected". If you tap the callout title, it also prints "right accessory selected". This does not seem correct.
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: 50.29, longitude: -107.79)
annotation.title = "Swift Current"
mapView.addAnnotation(annotation)
mapView.mapType = .standard
mapView.delegate = self
mapView.region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: annotation.coordinate.latitude,
longitude: annotation.coordinate.longitude),
span: MKCoordinateSpan(latitudeDelta: 1.0, longitudeDelta: 1.0)
)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let reuseId = "Annotation"
var view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
view?.canShowCallout = true
view?.leftCalloutAccessoryView = UIButton(type: .detailDisclosure)
//view?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
} else {
view?.annotation = annotation
}
return view
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.leftCalloutAccessoryView {
print("left accessory selected")
} else if control == view.rightCalloutAccessoryView {
print("right accessory selected")
}
}
}
It depends what kind of button you set on the rightCalloutAccessoryView. E.g. if you use:
[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
Then both tapping the callout and tapping the button both result in calloutAccessoryControlTapped being called with the button. But if you use:
[UIButton systemButtonWithImage:[UIImage systemImageNamed:#"info.circle"] target:nil action:nil];
Then only tapping the button will work, tapping the callout will be disabled.
If you have a custom button and you want the first behavior then you can make a button subclass and do this:
#interface UIButton2 : UIButton
#end
#implementation UIButton2
- (id)_mapkit_accessoryControlToExtendWithCallout{
return self;
}
#end
This private method is how it decides wether a button should also work for tapping the callout (learned this using Hopper). The default implementation checks self.buttonType to decide.
A more sensible way would be to start with the disclosure button and change its image, e.g.
UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
UIImage *image = [UIImage systemImageNamed:#"chevron.right" withConfiguration:[UIImageSymbolConfiguration configurationWithScale:UIImageSymbolScaleSmall];
[button setImage:image forState:UIControlStateNormal];
A lot of this weird behaviour is likely to do with changes in how callout buttons work since iOS 6.

How do I trigger a tapped event (on a map pin) without using callouts?

Is there a way to trigger a tapped event (on a map pin) without the use of callouts?
I tried to implement the didSelectAnnotationView below, but it did not seem to work:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if let annotation = annotation {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView = self.mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = false
view.enabled = true
}
return view
}
return nil
}
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
println("test")
}
The didSelectAnnotationView is the correct method. If you're not seeing it called, it's likely that the map view's delegate has not been set.
The default viewForAnnotation behavior will render something very close to what yours does, so it might not be immediately obvious that the delegate wasn't set properly. You might want to put a log/breakpoint in viewForAnnotation (or do something that makes it more visually distinct, e.g. different pin color) and confirm whether the delegate methods are getting called at all.

Resources