UILongPressGestureRecognizer does not work on MapKit (adapter) - ios

I have a view controller which gets an adapter object to be able to use MapKit.
On that adapter I implement 2 gesture (long press + tap) as always but only long press does not work.
How I prepare gestures:
private func prepareGestures(on pinView: PinViewProtocol) {
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(sender:)))
longPressGesture.delegate = self
longPressGesture.minimumPressDuration = Constants.longPressDuration
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
tapGesture.delegate = self
pinView.addGestureRecognizers([tapGesture, longPressGesture])
}
How I add:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? MyPinAnnotation else {
return nil
}
prepareGestures(on: pinView)
return pinView
}
How I add that object in my view controller:
private func configureMapView() {
mapViewAdapter = TestMapViewAdapter(mapView: mapView)
}

Related

Detect longpress in MapView for SwiftUI

I have a MapView in SwiftUi and I am trying to add a pin annotation to it when a user long presses a location on the map. I see this can easily be accomplished in swift however I am using SwiftUI. I do not know how to add the long-press detector. A code example would be great.
My MapView
struct MapView: UIViewRepresentable {
#Binding
var annotations: [PinAnnotation]
let addAnnotationListener: (PinAnnotation) -> Void
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
view.delegate = context.coordinator
view.addAnnotations(annotations)
if annotations.count == 1 {
let coords = annotations.first!.coordinate
let region = MKCoordinateRegion(center: coords, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
view.setRegion(region, animated: true)
}
}
func makeCoordinator() -> MapViewCoordinator {
MapViewCoordinator(self)
}
}
MapViewCoordinator
class MapViewCoordinator: NSObject, MKMapViewDelegate {
var mapViewController: MapView
init(_ control: MapView) {
self.mapViewController = control
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let annotation = view.annotation
guard let placemark = annotation as? MKPointAnnotation else { return }
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
//Custom View for Annotation
let identifier = "Placemark"
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
annotationView.annotation = annotation
return annotationView
} else {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.isEnabled = true
annotationView.canShowCallout = true
let button = UIButton(type: .infoDark)
annotationView.rightCalloutAccessoryView = button
return annotationView
}
}
}
The method to add a pin to a MapView
func addPinBasedOnGesture(gestureRecognizer:UIGestureRecognizer){
var touchPoint = gestureRecognizer.locationInView(mapView)
var newCoordinates = self.convertPoint(touchPoint, toCoordinateFromView: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinates
mapView.addAnnotation(annotation)
}
Below solution adds a pin at the point where user long presses on the Map.
Add below method in MapViewCoordinator
#objc func addPinBasedOnGesture(_ gestureRecognizer:UIGestureRecognizer) {
let touchPoint = gestureRecognizer.location(in: gestureRecognizer.view)
let newCoordinates = (gestureRecognizer.view as? MKMapView)?.convert(touchPoint, toCoordinateFrom: gestureRecognizer.view)
let annotation = PinAnnotation()
guard let _newCoordinates = newCoordinates else { return }
annotation.coordinate = _newCoordinates
mapViewController.annotations.append(annotation)
}
and longPress gesture code in func makeUIView(context: Context) -> MKMapView {}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
let longPressed = UILongPressGestureRecognizer(target: context.coordinator,
action: #selector(context.coordinator.addPinBasedOnGesture(_:)))
mapView.addGestureRecognizer(longPressed)
return mapView
}
Find below modified parts of provided code to get required behaviour:
struct SUMapView: UIViewRepresentable {
// ... other your code here
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
let longPressed = UILongPressGestureRecognizer(target:
context.coordinator, action: #selector(addPin(gesture:)))
mapView.addGestureRecognizer(longPressed)
return mapView
}
// ... other your code here
}
class MapViewCoordinator: NSObject, MKMapViewDelegate {
// ... other your code here
#objc func addPin(gesture: UILongPressGestureRecognizer) {
// do whatever needed here
}
// ... other your code here
}

Mapkit with LongPressGestureRecognizer working intermittently

I have a very simple setup, where I have a MapKit, and I'd like to be able to drop pin to it everytime the user do a long press on the map.
It works ok for the first pin. But if the user tried another long press (on another location), then the gesture is not recognized. The third time will work, the fourth time doesnt work, etc (every odd attempt will get recognized).
I have a sample project that demonstrate this:
https://github.com/alexwibowo/SimpleLocationMarker
Basically, my view controller looks like below:
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView! {
didSet{
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.addNewAnnotation(recognizer:)))
gestureRecognizer.minimumPressDuration = 0.5
mapView.addGestureRecognizer(gestureRecognizer)
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func addNewAnnotation(recognizer: UILongPressGestureRecognizer){
if (recognizer.state != .began) {
return
}
let touchPoint = recognizer.location(in: mapView)
let wayCoords = mapView.convert(touchPoint, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
annotation.title = "New"
annotation.coordinate = wayCoords
mapView.addAnnotation(annotation)
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "locationMarkerIdentifier"
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.annotation = annotation
view.canShowCallout = true
view.isDraggable = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
return view
}
}
I'm sure it is a very simple mistake I've done!
First, set its delegate to self.
#IBOutlet weak var mapView: MKMapView! {
didSet{
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.addNewAnnotation(recognizer:)))
gestureRecognizer.minimumPressDuration = 0.5
mapView.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.delegate = self
}
}
Second,
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}

Swift Annotation Pin's Calloutbubble Gesture

I am searching an answer for adding gesture to the calloutbubble of annotation pin.
I tried different solutions, but they did not work for me.
Here is the latest one:
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView{
let rightButton = UIButton(type: UIButtonType.detailDisclosure)
let gesture = UITapGestureRecognizer(target: self, action: #selector(callout(gesture:)))
rightButton.addGestureRecognizer(gesture)
view.rightCalloutAccessoryView = rightButton
}
#objc func callout(gesture: UITapGestureRecognizer){
print("tapped")
}
Correct delegate method
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
///
}
plus
self.mapView.delegate = self

Stop mapkit custom MKAnnotationView disappearing on tap

I currently have a custom MKAnnotationView set in a map callout, and it's working well. I however want to add a button to the callout view, but when i tap the button it closes the annotation view before it gets called. How can i get around this?
Here are pertinent bits of my code:
In my view for annotations:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "mapReuseId"
var mapView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if mapView == nil {
mapView = MKAnnotationView(annotation: annotation as! Annotation, reuseIdentifier: reuseId)
mapView!.canShowCallout = false
} else {
mapView!.annotation = annotation as! Annotation
}
mapDottView!.image = customImage
return mapDottView
}
In my didSelect delegate:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let callOut = customCallOutView(with: data)
view.addSubview(callOut)
// some layout here
}
The customCallOutView is longish, but the important part is that it has a UIButton which never gets called on tap. Any ideas?
You can try to put a delay when your button is tapped.
You can use Grand Central Dispatch or Perform Selector After Delay.
// Do what you need to do when your button was tapped.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Dismiss your annotation.
}
Try the following code:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "mapReuseId"
var mapView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if mapView == nil {
mapView = MKAnnotationView(annotation: annotation as! Annotation, reuseIdentifier: reuseId)
mapView!.canShowCallout = true
let rightButton: AnyObject! = UIButton(type: UIButtonType.detailDisclosure)
pinView?.rightCalloutAccessoryView = rightButton as? UIView
} else {
mapView!.annotation = annotation as! Annotation
}
mapDottView!.image = customImage
return mapDottView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print(#function)
// this method will be called when the button is tapped.
// annotation view doesn't disappear
}

UITapGestureRecognizer on subview of MKAnnotationView not working. SWIFT

I have a MKAnnotationView with an image subview.
I try to add an UITapGestureRecognizer to it but there is no response
var tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "annotationClicked:")
imageView.userInteractionEnabled = true
imageView.addGestureRecognizer(tapGestureRecognizer)
pinView.addSubview(imageView)
pinView.bringSubviewToFront(imageView)
I'm afraid I have no idea why
To react on the tap of an annotation view (leftCalloutAccessoryView or rightCalloutAccessoryView) you have to create the view as a descendant of UIControl. Then you can implement the calloutAccessoryControlTapped method of the MKMapViewDelegate protocol. No need to use a gesture recognizer.
Here is a code snipped which adds a button as a callout accessory to a pinAnnotation:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is PinAnnotation {
let pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myPin")
pinAnnotationView.pinColor = .Purple
pinAnnotationView.draggable = true
pinAnnotationView.canShowCallout = true
pinAnnotationView.animatesDrop = true
// button as callout accessory
let deleteButton = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
deleteButton.frame.size.width = 44
deleteButton.frame.size.height = 44
deleteButton.backgroundColor = UIColor.redColor()
deleteButton.setImage(UIImage(named: "trash"), forState: .Normal)
pinAnnotationView.leftCalloutAccessoryView = deleteButton
return pinAnnotationView
}
return nil
}
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
if let annotation = view.annotation as? PinAnnotation {
self.mapView.removeAnnotation(annotation)
}
}

Resources