I am trying to create a pin annotation with callout that is clickable as whole part. I have written it like this, but it doesn't as expected - you need to tap on the button for mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) to be called
func mapView(mapView: MKMapView!, viewForAnnotation annotation: JJMapAnnotation!) -> MKAnnotationView! {
if annotation.isKindOfClass(MKUserLocation) {
return nil
}
var annotationIdentifier = "AnnotationIdentifier";
var pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
var rightButton = UIButton.buttonWithType(UIButtonType.DetailDisclosure) as UIButton
pinView.rightCalloutAccessoryView = rightButton;
pinView.animatesDrop = true;
pinView.canShowCallout = true;
return pinView
}
Which gives me this:
I think it should be possible, because that is the behavior I am observing with Apple maps. You can click wherever on their annotation's callout to be taken to detail view.
Also - when I click anywhere besides the button on callout it behaves as a button - it highlights for the time of tap. But the calloutAccessoryControlTapped won't be called and I didn't find any API function that could be called for that event.
I tried with the approach of adding some UIGestureRecognizers, but that doesn't really work considering the callout view is created in flight and is not an annotation view, so there is really no place where to hook it.
Edit: JJMapAnnotation is a subclass of MKPointAnnotation
Related
I want to show a UIView just above the pin location and if the user moves around the map the UIView should remain above the pin location. I dont want to use the callout bubble. Is there any other way?
in iOS 9 we have a new property named detailCalloutAccessoryView
You can create a view and set as
annotationView.detailCalloutAccessoryView = tempView
Please check the link to get more details
MapKit iOS 9 detailCalloutAccessoryView usage
Try using this code:
func mapView(_ mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView {
if (annotation is MKUserLocation) {
return nil
}
else if (annotation is YourAnnotationClassHere) {
let identifier = "MyCustomAnnotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView {
annotationView.annotation = annotation
}
else {
annotationView = MKAnnotationView(annotation, reuseIdentifier: identifier)
}
annotationView.canShowCallout = false
// set to YES if using customized rendition of standard callout; set to NO if creating your own callout from scratch
annotationView.image = UIImage(named: "your-image-here.png")!
return annotationView
}
return nil
}
That's mainly what you need for that to work. This is a swift version of this answer right here: How to create Custom MKAnnotationView and custom annotation title and subtitle
Hope it helped!!!
I am trying to customize the annotations on the map, and instead of pins I want the annotations to look like the current location indicator. How would I do this? Any advice would be great!
In general you can use custom annotation object which extends MKPointAnnotation. But if you just need to change pin image you can avoid subclassing and just implement this method
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "MyPin"
if annotation.isKindOfClass(MKUserLocation) {
return nil
}
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if let annotationView = annotationView {
annotationView.annotation = annotation
} else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.image = UIImage(named: "myPinImage")
}
return annotationView
}
So it's enough for you to find correct image and replace pins with it.
But if you want to follow possible changes of default currentLocation image you can reuse default view
let annotationView = mapView.viewForAnnotation(mapView.userLocation());
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.
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.
I have a working loop to setup annotations for the title and subtitle elements for some working data points. What I want to do within that same loop structure is to set the pin color to Purple instead of the default. What I can't figure out is what I need to do to tap into my theMapView to set the pin accordingly.
My working loop and some attempts at something...
....
for var index = 0; index < MySupplierData.count; ++index {
// Establish an Annotation
myAnnotation = MKPointAnnotation();
... establish the coordinate,title, subtitle properties - this all works
self.theMapView.addAnnotation(myAnnotation) // this works great.
// In thinking about PinView and how to set it up I have this...
myPinView = MKPinAnnotationView();
myPinView.animatesDrop = true;
myPinView.pinColor = MKPinAnnotationColor.Purple;
// Now how do I get this view to be used for this particular Annotation in theMapView that I am iterating through??? Somehow I need to marry them or know how to replace these attributes directly without the above code for each data point added to the view
// It would be nice to have some kind of addPinView.
}
You need to implement the viewForAnnotation delegate method and return an MKAnnotationView (or subclass) from there.
This is just like in Objective-C -- the underlying SDK works the same way.
Remove the creation of MKPinAnnotationView from the for loop that adds the annotations and implement the delegate method instead.
Here is a sample implementation of the viewForAnnotation delegate method in Swift:
func mapView(mapView: MKMapView!,
viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.pinColor = .Purple
}
else {
pinView!.annotation = annotation
}
return pinView
}