UILongPressGestureRecognizer is getting fired twice when user long presses on a map for over 2-4 seconds. How can I ensure it will only be fired once?
func action(gestureRecognizer:UIGestureRecognizer) {
println("long pressed on map")
override func viewDidLoad() {
super.viewDidLoad()
manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
if activePlace == -1 {
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
} else {
var uilpgr = UILongPressGestureRecognizer(target: self, action: "action:")
uilpgr.minimumPressDuration = 2.0
myMap.addGestureRecognizer(uilpgr)
}
}
func action(gestureRecognizer:UIGestureRecognizer) {
println("long pressed on map")
var touchPoint = gestureRecognizer.locationInView(self.myMap)
var newCoordinate = myMap.convertPoint(touchPoint, toCoordinateFromView: self.myMap)
var annotation = MKPointAnnotation()
annotation.coordinate = newCoordinate
//annotation.title = "New Place"
myMap.addAnnotation(annotation)
var loc = CLLocation(latitude: newCoordinate.latitude, longitude: newCoordinate.longitude)
}
You have to check the gesture recognizer´s state for the begin of the gesture:
func action(gestureRecognizer:UIGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.Began {
// ...
}
}
Long-press gestures are continuous. The gesture begins (UIGestureRecognizerStateBegan) when the number of allowable fingers (numberOfTouchesRequired) have been pressed for the specified period (minimumPressDuration) and the touches do not move beyond the allowable range of movement (allowableMovement). The gesture recognizer transitions to the Change state whenever a finger moves, and it ends (UIGestureRecognizerStateEnded) when any of the fingers are lifted.
try something like this:
let longGesture = UILongPressGestureRecognizer(target : self,
action : #selector(someFunc(gestureRecognizer:)))
func someFunc(gestureRecognizer: UILongPressGestureRecognizer){
if gestureRecognizer.state == .began {
//do something
}
Related
I'm making an app that measures speed and distance in real time. so before did that app starts updating location when button tapped. but this method takes time after button tapped. if i will put locationManager.startUpdatingLocation in viewDidLoad, speed and distance starts measure immediately without tapping start button. How can I do like speed and distance starts measure immediately ONLY WHEN when start button tapped? here's my code.
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
#IBAction func startStopButtonDidTouch(_ sender: UIButton) {
if isStarted { //When tapped STOP
locationManager.stopUpdatingLocation()
startStopButton.setTitle("START", for: .normal)
} else { //When tapped START
startStopButton.setTitle("STOP", for: .normal)
}
}
func locationManager (_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
speedLabel.text = "\(Int(location.speed * 3.6))"
if (startLocation == nil) {
startLocation = location;
}
let endLocation = location;
let distance = startLocation.distance(from: endLocation) / 1000 //m->km
distanceLabel.text = "\(String(format: "%.1f", distance)) km"
}
I'm not sure this is the best solution, but you might use an extra variable to make sure that when you are getting the location on app start, the speed and distance calculations should not be done. Something like this:
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
isToPerformCalculations = false
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
#IBAction func startStopButtonDidTouch(_ sender: UIButton) {
if isStarted { //When tapped STOP
// locationManager.stopUpdatingLocation()
startStopButton.setTitle("START", for: .normal)
} else { //When tapped START
isToPerformCalculations = true
startStopButton.setTitle("STOP", for: .normal)
}
}
func locationManager (_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
locationManager.stopUpdatingLocation()
if isToPerformCalculations {
if (startLocation == nil) {
startLocation = location;
}
let endLocation = location;
speedLabel.text = "\(Int(location.speed * 3.6))"
let distance = startLocation.distance(from: endLocation) / 1000 //m->km
distanceLabel.text = "\(String(format: "%.1f", distance)) km"
}
}
I also changed where you stopUpdatingLocation, see if this works for you.
I know how to simply detect a long press, but it detect after release. How can I detect the long press without releasing the finger?
This is the code I am using for long press now:
override func viewDidLoad() {
super.viewDidLoad()
setupLongPressGesture()
}
func setupLongPressGesture() {
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress))
longPressGesture.minimumPressDuration = 1.0 // 1 second press
longPressGesture.delegate = self
self.tableView.addGestureRecognizer(longPressGesture)
}
#objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer){
if gestureRecognizer.state == .ended {
let touchPoint = gestureRecognizer.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
}
}
}
Change .ended to .began.
From the documentation for UILongPressGestureRecognizer:
Long-press gestures are continuous. The gesture begins (UIGestureRecognizer.State.began) when the number of allowable fingers (numberOfTouchesRequired) have been pressed for the specified period (minimumPressDuration) and the touches do not move beyond the allowable range of movement (allowableMovement). The gesture recognizer transitions to the Change state whenever a finger moves, and it ends (UIGestureRecognizer.State.ended) when any of the fingers are lifted.
I want to use double click. I have written function doubleTap. How to recognize location of finger?
override func viewDidLoad()
{
super.viewDidLoad()
let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTap")
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTap()
{
}
You can use location(ofTouch:in:) to get the location of the touch. However, you need access to the gesture recognizer from inside the function where you want to access the location, so you should declare doubleTap as an instance property of your class.
class YourViewController: UIViewController {
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(YourViewController.doubleTap))
override func viewDidLoad(){
super.viewDidLoad()
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTap(){
let touchLocation = doubleTap.location(ofTouch: numberOfTouches-1,in: nil)
}
}
Change the input parameters to the function if you want to change whether you need to get the first or last touch's location or if you want to get the location relative to a subview.
You can get the gesture recognizer as a parameter
override func viewDidLoad()
{
super.viewDidLoad()
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleTapFunc))
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
view.addGestureRecognizer(doubleTap)
}
func doubleTapFunc(_ sender: UITapGestureRecognizer)
{
// Use 'sender' here to get the location
if sender.state == .ended {
// handling code
let location = sender.location(ofTouch: 0, in: self.view!)
}
}
I have a issue with my swipe gesture on a MapView, to be more accurate I have issues with the first swipe gesture on a MapView because it does not does a function & moves the screen at the same time.
The code recognize my location, put an annotation on me, and runs a 5 sec timer that allows the app to add new annotations only every 5 secs. The MapView follows my path as usually. All good so far. Check the pic of my simulator at the end.
Then it occurred to me that it would be a good idea to allow the user to scape from that region and explore the rest of the map. The user can do that only swiping in any direction. And when the user is at any other location he can press the "getBack" button to center the map on his current location. All works good too.
The issue is that when the gesture Swipe is detected , that first swipe does not move the screen. It is only the second swift which does it.
I think that may be the first swipe only stops the map from following my path, and then the second one is the one that moves the screen.
Is there a way for the first swipe to stop following my path and move the map at the same time ?
I hope I made myself clear :(
The Code of the ViewController:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var map: MKMapView!
var locationManager = CLLocationManager()
let latDelta:CLLocationDegrees = 0.05
let longDelta:CLLocationDegrees = 0.05
var reStartCountClock = true
var getBackInPlace = true
var timer = NSTimer()
#IBAction func getBack(sender: AnyObject) {
getBackInPlace = true
}
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
let uilpgr = UILongPressGestureRecognizer(target: self, action: "actionWhenPressed:")
uilpgr.minimumPressDuration = 1
map.addGestureRecognizer(uilpgr)
let uilpgrD = UISwipeGestureRecognizer(target: self, action:Selector("handleSwipe:"))
uilpgrD.direction = .Down
let uilpgrU = UISwipeGestureRecognizer(target: self, action:Selector("handleSwipe:"))
uilpgrU.direction = .Up
let uilpgrR = UISwipeGestureRecognizer(target: self, action:Selector("handleSwipe:"))
uilpgrR.direction = .Right
let uilpgrL = UISwipeGestureRecognizer(target: self, action:Selector("handleSwipe:"))
uilpgrL.direction = .Left
map.addGestureRecognizer(uilpgrD)
map.addGestureRecognizer(uilpgrR)
map.addGestureRecognizer(uilpgrL)
map.addGestureRecognizer(uilpgrU)
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locationOnline:CLLocation = locations[0]
let spanOnline:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
let centerOnline:CLLocationCoordinate2D = CLLocationCoordinate2DMake(locationOnline.coordinate.latitude, locationOnline.coordinate.longitude)
let regionOnline:MKCoordinateRegion = MKCoordinateRegionMake(centerOnline, spanOnline)
if getBackInPlace == true {
self.map.setRegion(regionOnline, animated: true)
}
if reStartCountClock == true {
timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "Anota", userInfo: nil, repeats: true)
reStartCountClock = false
let annotationOnline = MKPointAnnotation()
annotationOnline.title = "Updated Location"
annotationOnline.subtitle = "Pin Pam Pum"
annotationOnline.coordinate = centerOnline
self.map.addAnnotation(annotationOnline)
}
}
func handleSwipe (gestureRecognizer: UIGestureRecognizer)
{
getBackInPlace = false
print("Entra en el gesto")
}
func Anota (){
reStartCountClock = true
timer.invalidate()
}
func actionWhenPressed (gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.Began
{
getBackInPlace = false
print("Gesture Recognized")
let touch = gestureRecognizer.locationInView(self.map)
let touchCoordinate:CLLocationCoordinate2D = map.convertPoint(touch, toCoordinateFromView: self.map)
let touchAnnotation = MKPointAnnotation()
touchAnnotation.coordinate = touchCoordinate
touchAnnotation.title = "You tapped here"
touchAnnotation.subtitle = "It is a recommended place"
self.map.addAnnotation(touchAnnotation)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The Simulator:
I am trying to use a number of UIGestureReconizers with an MKMapView onto which the user can drop a pin and drag it around. It has a callout. I am implementing this in a TabbarController and also within a NavigationController. I currently have:
1) A PanGestureRecognizer animates the Tabbar and Navigation item off the screen. This works fine without interfering with panning the map.
2) A TapGestureRecognizer set to one tap animates the two items from 1) back onto the screen.
3) A TapGestureRecognizer set to two taps allows the underlying MKMapView zoom functionality to work. This GestureRecognizer's delegate has gestureRecognizer.shouldRecognizeSimultaneouslyWithGestureRecognizer set to true
These are setup in viewDidLoad as follows:
// This sets up the pan gesture recognizer to hide the bars from the UI.
let panRec: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: "didDragMap:")
panRec.delegate = self
mapView.addGestureRecognizer(panRec)
// This sets up the tap gesture recognizer to un-hide the bars from the UI.
let singleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "didTapMap:")
singleTap.delegate = self
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
mapView.addGestureRecognizer(singleTap)
// This sets up the double tap gesture recognizer to enable the zoom facility.
// In order to pass double-taps to the underlying `MKMapView` the delegate for this recognizer (self) needs to return true from
// gestureRecognizer.shouldRecognizeSimultaneouslyWithGestureRecognizer
let doubleTap: UITapGestureRecognizer = UITapGestureRecognizer()
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
doubleTap.delegate = self
mapView.addGestureRecognizer(doubleTap)
// This delays the single-tap recognizer slightly and ensures that it will NOT fire if there is a double-tap
singleTap.requireGestureRecognizerToFail(doubleTap)
My problem occurs when I try to implement a UILongPressGestureRecognizer to allow the dropping of a pin onto the map. I'm trying to use the following added to viewDidLoad:
// This sets up the long tap to drop the pin.
let longTap: UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: "didLongTapMap:")
longTap.delegate = self
longTap.numberOfTapsRequired = 0
longTap.minimumPressDuration = 0.5
mapView.addGestureRecognizer(longTap)
This is my action method:
func didLongTapMap(gestureRecognizer: UIGestureRecognizer) {
// Get the spot that was tapped.
let tapPoint: CGPoint = gestureRecognizer.locationInView(mapView)
let touchMapCoordinate: CLLocationCoordinate2D = mapView.convertPoint(tapPoint, toCoordinateFromView: mapView)
var viewAtBottomOfHierarchy: UIView = mapView.hitTest(tapPoint, withEvent: nil)
if let viewAtBottom = viewAtBottomOfHierarchy as? MKPinAnnotationView {
return
} else {
if .Began == gestureRecognizer.state {
// Delete any existing annotations.
if mapView.annotations.count != 0 {
mapView.removeAnnotations(mapView.annotations)
}
annotation = MKPointAnnotation()
annotation.coordinate = touchMapCoordinate
mapView.addAnnotation(annotation)
_isPinOnMap = true
findAddressFromCoordinate(annotation.coordinate)
updateLabels()
}
}
}
This does indeed allow a pin to be dropped on a long tap and a single tap will display the callout BUT a second tap to hold and drag causes a second pin to drop if the drag isn't started sufficiently quickly. This second pin drops into the space the previous pin was hovering in and can be dragged by the user, but the new pin dropping is awkward and wrong looking.
I'm trying to use the line:
if let viewAtBottom = viewAtBottomOfHierarchy as? MKPinAnnotationView {
to return the tap to the MKMapView and prevent another pin being dropped but the return never gets called even though a breakpoint on this line shows viewAtBottom is of type MapKit.MKPinAnnotationView. Any ideas where I'm going wrong?
I think I might have the answer to your problem if I understood it correctly.
Your having problems when one pin is dropped and then dragging the screen around without placing another pin, correct?
This is my code, I have been making something similar and this seems to work for me.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var map: MKMapView!
var manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longpress(gestureRecognizer:)))
uilpgr.minimumPressDuration = 2
map.addGestureRecognizer(uilpgr)
if activePlace == -1 {
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
self.map.showsUserLocation = true
} else {
//GET PLACE DETAILS TO DISPLAY ON MAP
if places.count > activePlace {
if let name = places[activePlace]["name"]{
if let lat = places[activePlace]["lat"]{
if let lon = places[activePlace]["lon"]{
if let latitude = Double(lat) {
if let longitude = Double(lon) {
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region = MKCoordinateRegionMake(coordinate, span)
self.map.setRegion(region, animated: true)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = name
self.map.addAnnotation(annotation)
}
}
}
}
}
}
}
}
func longpress(gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.began {
let touchPoint = gestureRecognizer.location(in: self.map)
let newCoordinate = self.map.convert(touchPoint, toCoordinateFrom: self.map)
let location = CLLocation(latitude: newCoordinate.latitude, longitude: newCoordinate.longitude)
var title = ""
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) in
if error != nil {
print(error)
} else {
if let placemark = placemarks?[0] {
if placemark.subThoroughfare != nil {
title += placemark.subThoroughfare! + " "
}
if placemark.thoroughfare != nil {
title += placemark.thoroughfare! + " "
}
}
}
if title == "" {
title = "Added \(NSDate())"
}
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinate
annotation.title = title
self.map.addAnnotation(annotation)
places.append(["name": title, "lat":String(newCoordinate.latitude), "lon":String(newCoordinate.longitude)])
UserDefaults.standard.set(places, forKey: "places")
})
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = CLLocationCoordinate2D(latitude: locations[0].coordinate.latitude, longitude: locations[0].coordinate.longitude)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: location, span: span)
self.map.setRegion(region, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Hope it helps, the problem might also be that your minimum longpress duration is only 0.5.