PointAnnotation callout on MKMapView appears and then immidiately disappears - ios

I am creating a simple point annotation with a callout inside the UITapGestureRecognizer delegate.
The first time I tap on the map, the pin appears with the callout but the callout immediately disappears after that.
The second time I tap on the same pin, the callout appears and stays there, not sure why it disappears at the first time.
#IBAction func handleMapTouch(recognizer: UITapGestureRecognizer){
let view = recognizer.view
let touchPoint=recognizer.locationInView(view)
var touchCord=CLLocationCoordinate2D()
touchCord = mapView.convertPoint(touchPoint, toCoordinateFromView:
mapView)
mapView.removeAnnotations(mapView.annotations)
pointAnnotation.coordinate=touchCord
pointAnnotation.title="ABC"
pointAnnotation.subtitle="DEF"
mapView.addAnnotation(pointAnnotation)
mapView.selectAnnotation(pointAnnotation, animated: true)
}

Just in case someone else has the same problem, although Keith's answer works, in my case it disrupts other gestures associated to the map, like pinch and zoom.
For me, delaying some milliseconds the action of showing the callout worked better.
In Swift 3:
let deadlineTime = DispatchTime.now() + .milliseconds(500)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
mapView.addAnnotation(pointAnnotation)
mapView.selectAnnotation(pointAnnotation, animated: true)
}

I have the same problem. I don't know how to solve it, either, but I found a workaround. Maybe it can help you too.
I used LongPressGesture to replace TapGesture
In Viewdidload:
let longPress = UILongPressGestureRecognizer(target: self, action: "addAnnotation:")
longPress.minimumPressDuration = 0.1
self.mapView.addGestureRecognizer(longPress)
In function addAnnotation:
if(gestureRecognizer.state == .Ended){
self.mapView.removeGestureRecognizer(gestureRecognizer)
//remove all annotation on the map
self.mapView.removeAnnotations(self.mapView.annotations)
//convert point user tapped to coorinate
let touchPoint: CGPoint! = gestureRecognizer.locationInView(self.mapView)
let touchMapCoordinate: CLLocationCoordinate2D = self.mapView.convertPoint(touchPoint, toCoordinateFromView: self.mapView)
showCustomAnnotation(touchMapCoordinate)
}
self.mapView.addGestureRecognizer(gestureRecognizer)

Related

Long Press Gesture in ArcGis Map in IOS

I'm working with ArcGis Map in my ios app. I'm trying to apply a long press gesture on it. But i'm getting an error Value of type 'BCBaseMapView?' has no member 'addGestureRecognizer' . How i can add long gesture on it. This is what i coded in it.
let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
lpgr.minimumPressDuration = 0.5
lpgr.delaysTouchesBegan = true
// lpgr.delegate = self
self.mapView.addGestureRecognizer(lpgr)
#objc func handleLongPress(sender: UILongPressGestureRecognizer)
{
print("longpressed")
self.addWaypointOnMap()
}
This is my code i'm getting error on this line.
self.mapView.addGestureRecognizer(lpgr)
This is my mapView
var mapView: BCBaseMapView?
If the superview of BCBaseMapView is MKMapView then try to add it on super view using:
self.mapView.super.addGestureRecognizer(lpgr)
OR
self.mapView.superview.addGestureRecognizer(lpgr)

Swift: Map returns 4 values as the distance between 2 points, why?

I am calculatig the distance between 2 points. For the first point, I am assigning the latitude and the longtiude. The second point coordinates wherever the user clicks on the map. Every thing works perfectly, except the answer being printed 4 times.
I tried to return the first index(it turned out the first index is 4
values).
I tried to add these values to a set to delete the repeated ones (it did not work).
I tried to make a loop, and break after the first time (Still return 4 values).
Here where I am creating the annotation for the user click (inside viewdidAppear):
,
let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(MapViewController.longpress(gestureRecognizer:)))
uilpgr.minimumPressDuration = 2
map.addGestureRecognizer(uilpgr)
And this is the function that calculates the distance (After the function viewdidAppear):
#objc func longpress(gestureRecognizer: UIGestureRecognizer){
let touchPoint = gestureRecognizer.location(in: self.map)
let coordinates = map.convert(touchPoint, toCoordinateFrom: self.map)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinates
annotation.title = "Destination"
map.addAnnotation(annotation)
let source = CLLocation(latitude: 5, longitude: 5)
let distination = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
let distanceInMeters = source.distance(from: distination)
let distanceInMiles = String(Int(distanceInMeters/1.6))
myArrayDataStructure.myArray.append(distanceInMiles)
UserDefaults.standard.set(myArrayDataStructure.myArray, forKey: "items")
}
Your problem is how you handle the gesture. A "long press" gesture (like others) has several states. A gesture action is called when the gesture begins, when it ends, when it is cancelled, and in the case of a "long press", as the gesture is updated.
You want to perform your distance calculations only when the gesture's state is "ended".
#objc func longpress(gestureRecognizer: UIGestureRecognizer){
if gestureRecognizer.state = .ended {
// your code here
}
}

Detecting taps on an animating UIImageView

I am using a custom path animation on UIImageView items for a Swift 3 project. The code outline is as follows:
// parentView and other parameters are configured externally
let imageView = UIImageView(image: image)
imageView.isUserInteractionEnabled = true
let gr = UITapGestureRecognizer(target: self, action: #selector(onTap(gesture:)))
parentView.addGestureRecognizer(gr)
parentView.addSubview(imageView)
// Then I set up animation, including:
let animation = CAKeyframeAnimation(keyPath: "position")
// .... eventually ....
imageView.layer.add(animation, forKey: nil)
The onTap method is declared in a standard way:
func onTap(gesture:UITapGestureRecognizer) {
print("ImageView frame is \(self.imageView.layer.visibleRect)")
print("Gesture occurred at \(gesture.location(in: FloatingImageHandler.parentView))")
}
The problem is that each time I call addGestureRecognizer, the previous gesture recognizer gets overwritten, so any detected tap always points to the LAST added image, and the location is not detected accurately (so if someone tapped anywhere on the parentView, it would still trigger the onTap method).
How can I detect a tap accurately on per-imageView basis? I cannot use UIView.animate or other methods due to a custom path animation requirement, and I also cannot create an overlay transparent UIView to cover the parent view as I need these "floaters" to not swallow the events.
It is not very clear what are you trying to achieve, but i think you should add gesture recognizer to an imageView and not to a parentView.
So this:
parentView.addGestureRecognizer(gr)
Should be replaced by this:
imageView.addGestureRecognizer(gr)
And in your onTap function you probably should do something like this:
print("ImageView frame is \(gesture.view.layer.visibleRect)")
print("Gesture occurred at \(gesture.location(in: gesture.view))")
I think you can check the tap location that belongs imageView or not on the onTap function.
Like this:
func ontap(gesture:UITapGestureRecognizer) {
let point = gesture.location(in: parentView)
if imageView.layer.frame.contains(point) {
print("ImageView frame is \(self.imageView.layer.visibleRect)")
print("Gesture occurred at \(point)")
}
}
As the layers don't update their frame/position etc, I needed to add the following in the image view subclass I wrote (FloatingImageView):
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
let prespt = self.superview!.layer.convert(suppt, to: pres)
return super.hitTest(prespt, with: event)
}
I also moved the gesture recognizer to the parent view so there was only one GR at any time, and created a unique tag for each of the subviews being added. The handler looks like the following:
func onTap(gesture:UITapGestureRecognizer) {
let p = gesture.location(in: gesture.view)
let v = gesture.view?.hitTest(p, with: nil)
if let v = v as? FloatingImageView {
print("The tapped view was \(v.tag)")
}
}
where FloatingImageView is the UIImageView subclass.
This method was described in an iOS 10 book (as well as in WWDC), and works for iOS 9 as well. I am still evaluating UIViewPropertyAnimator based tap detection, so if you can give me an example of how to use UIViewPropertyAnimator to do the above, I will mark your answer as the correct one.

ShinobiCharts data labels/annotations/Tick Mark gesture recognizer

Using shinobi charts
Looking for examples how to add gesture recognizers (on touch up) to Tick Marks and schart annoations
I see the documentation for interacting with a series data series, but I need to add GestureRecognizers to tick marks and annotation events
I tried this for the tickMark/datapoint labels with no luck:
func sChart(chart: ShinobiChart!, alterTickMark tickMark: SChartTickMark!, beforeAddingToAxis axis: SChartAxis!) {
if let label = tickMark.tickLabel {
//added a gesture recognizer here but it didn't work
}
For the SchartAnnotations no idea how to go about adding one there
I think you're nearly there with labels. I found I just needed to set userInteractionEnabled = true e.g.
func sChart(chart: ShinobiChart!, alterTickMark tickMark: SChartTickMark!, beforeAddingToAxis axis: SChartAxis!) {
if let label = tickMark.tickLabel {
let tapRecognizer = UITapGestureRecognizer(target: self, action: "labelTapped")
tapRecognizer.numberOfTapsRequired = 1
label.addGestureRecognizer(tapRecognizer)
label.userInteractionEnabled = true
}
}
Annotations are a little trickier, as they're on a view below SChartCanvasOverlay (responsible for listening for gestures). This results in the gestures being 'swallowed' before they get to the annotation.
It is possible however, but you'll need to add a UITapGestureRecognizer to your chart and then loop through the chart's annotations to check whether the touch point was inside an annotation. E.g.:
In viewDidLoad:
let chartTapRecognizer = UITapGestureRecognizer(target: self, action: "annotationTapped:")
chartTapRecognizer.numberOfTapsRequired = 1
chart.addGestureRecognizer(chartTapRecognizer)
And then the annotationTapped function:
func annotationTapped(recognizer: UITapGestureRecognizer) {
var touchPoint: CGPoint?
// Grab the first annotation so we can grab its superview for later use
if let firstAnnotation = chart.getAnnotations().first as? UIView {
// Convert touch point to position on annotation's superview
let glView = firstAnnotation.superview!
touchPoint = recognizer.locationInView(glView)
}
if let touchPoint = touchPoint {
// Loop through the annotations
for item in chart.getAnnotations() {
let annotation: SChartAnnotation = item as SChartAnnotation
if (CGRectContainsPoint(annotation.frame, touchPoint as CGPoint)) {
chart.removeAnnotation(annotation)
}
}
}
}

How to disable Google Maps double tap zooming?

How to disable Google Maps SDK double tap zooming (but not pinch zooming) in iOS SDK?
Thank You
For Swift 2.1
You can do this on your GMSMapView object,
mapView.settings.zoomGestures = false
Same goes for disabling tilt gesture, rotate gesture, scroll gestures,
mapView.settings.tiltGestures = false
mapView.settings.rotateGestures = false
mapView.settings.scrollGestures = false
Read more here: https://developers.google.com/maps/documentation/ios-sdk/controls#map_gestures
Unfortunately there is no way to disable double tap zoom while still keeping pinch zoom gestures. (I could be wrong, but I have went through their docs and haven't found a way to do so)
try this :
[googleMapView.settings setAllGesturesEnabled:NO];
install your own tapgesturerecognizer and make it receive double taps. that way it won't be given to the google map.
put this in controller
/// this gesture will disable zoom gesture temporarily
var tmpDisableZoom: UITapGestureRecognizer!
/// how long you want to lock
let lockDoubleTapTimeDelay = 0.3
/// finally unlock time
var unlockDoubleTapTime = Date().timeIntervalSince1970
override func viewDidLoad() {
mapView.settings.consumesGesturesInView = false
tmpDisableZoom = UITapGestureRecognizer(target: self, action: #selector(removeZoomGesturesTemporarily))
tmpDisableZoom.numberOfTapsRequired = 1
}
#objc func removeZoomGesturesTemporarily(sender:UIGestureRecognizer){
mapView.settings.zoomGestures = false
unlockDoubleTapTime = Date().timeIntervalSince1970
DispatchQueue.main.asyncAfter(deadline: .now() + lockDoubleTapTimeDelay) { [weak self] in
guard self != nil else {return}
let timeNow = Date().timeIntervalSince1970
if timeNow - self!.unlockDoubleTapTime > self!.lockDoubleTapTimeDelay{
self!.mapView.settings.zoomGestures = true
}
}
}

Resources