How do I add an action to a uiview that will run if a screen interaction (long press) moves to a uiview and then stays there for a select period of time.
EDIT: I have a drop down menu sort of system and a long press on the button reveals the menu. Then a user would slide down onto an option and then release the tap. I want to tell which menu item that happened on and act accordingly.
Add the below gesture to your UIView for long press functionality.
var longTap = UILongPressGestureRecognizer(target: self, action: #selector(self.longTouch))
longTap.numberOfTapsRequired = 0 // Set your own number here
longTap.minimumPressDuration = 1.0 //Set your duration here
longTap.delegate = self// Add the <UIGestureRecognizerDelegate> protocol
self.view.addGestureRecognizer(longTap)
Add the delegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Below is selector will be called
func longTouch(_ recognizer: UILongPressGestureRecognizer) {
if recognizer.state == .began {
print("longTouch UIGestureRecognizerStateBegan")
}
if recognizer.state == .ended {
print("longTouch UIGestureRecognizerStateEnded")
}
}
Objective C version
UILongPressGestureRecognizer *longTap = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longTouch:)];
[longTap setNumberOfTapsRequired:0]; // Set your own number here
[longTap setMinimumPressDuration:1.0]; // Set your duration here
[longTap setDelegate:self]; // Add the <UIGestureRecognizerDelegate> protocol
[self.view addGestureRecognizer:longTap];
Delgate:-
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Selector:-
- (void) longTouch: (UILongPressGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan)
{
NSLog(#"longTouch UIGestureRecognizerStateBegan");
}
if (recognizer.state == UIGestureRecognizerStateEnded)
{
NSLog(#"longTouch UIGestureRecognizerStateEnded");
}
}
Requirment on iOS for mapBox map. (I am not talking about MKMapView)
how can we detect if singleTap was tapped or annotation on mapView? I need that singleTap will be handled only on empty area of map (without pins), and didSelectAnnotation called when i tap on pin.
But I found on android we have method like this
mapboxMap.setOnMapClickListener(new MapboxMap.OnMapClickListener() {
public void onMapClick(#NonNull LatLng point) {
Toast.makeText(getActivity(),"on Tap "+point.getLatitude(),Toast.LENGTH_LONG).show();
}
});
and along with that
mapboxMap.setInfoWindowAdapter(new MapboxMap.InfoWindowAdapter() { ... }) will display the annotation.
Don't we have same kind of concept in iOS ?
The actual problem is in iOS is, when I add singleTapGesture on mapView of Mapbox
UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[self.mapView addGestureRecognizer:singleTapGesture];
the delegate method of mapView of mapbox will not call.
- (nullable UIView <MGLCalloutView> *)mapView:(MGLMapView *)mapView calloutViewForAnnotation:(id <MGLAnnotation>)annotation;
to make sure the delegate method must call, then I dont have to use singleTapGesture
Here the situation is either this or that, but as per me need I needed both.
Looking forward of any solution.
Thanks,
Subclass MGLMapView and delegate its' touchesEnded.
protocol MapViewTapDelegate: class {
func mapViewTapped(withTouchLocation touchLocation: CLLocationCoordinate2D)
}
class MapView: MGLMapView {
weak var tapDelegate: MapViewTapDelegate?
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
guard let touch = touches.first else { return }
let touchLocation = convert(touch.location(in: self), toCoordinateFrom: nil)
tapDelegate?.mapViewTapped(withTouchLocation: touchLocation)
}
}
As touchesEnded isn't triggered when didSelect annotation is called and vice versa, this is what you need.
class ViewController: UIViewController {
#IBOutlet weak var mapView: MapView! {
didSet {
mapView.delegate = self; mapView.tapDelegate = self
}
}
}
extension ViewController: MGLMapViewDelegate {
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
print("didSelect annotationWithCoordinate: \(annotation.coordinate)")
}
}
extension ViewController: MapViewTapDelegate {
func mapViewTapped(withTouchLocation touchLocation: CLLocationCoordinate2D) {
print("mapViewTapped touchLocation: \(touchLocation)")
}
}
I think that the method
-(void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id<MGLAnnotation>)annotation will solve your problem for selecting annotation.
I made another workaround here
I cloned the release-ios-v3.3.0 and created package using Building the SDK and added one delegate method in
MGLMapvViewDelegate.h
as per my need something like that.
-(void)mapView:(MGLMapView *)mapView tapOnNonAnnotationAreaWithPoints:(CGPoint)points
and in MGLMapView.mm I updated the code like that,
else{
if(self.selectedAnnotation)
[self deselectAnnotation:self.selectedAnnotation animated:YES];
else if([self.delegate respondsToSelector:#selector(mapView:tapOnNonAnnotationAreaWithPoints:)])
[self.delegate mapView:self tapOnNonAnnotationAreaWithPoints:tapPoint];
}
which is in -(void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap
method.
Its working for me, as I am able to detect single tap on non annotation area. later I convert the passed points to geo coordinates, so that I work with newly tapped coordinate.
Note
Newly Created library size is about 48.7 MB where as downloaded library was 38.3 MB.
Style is not working properly, you can see in attached pic. (Tried with different Style URL but none of it is same as previous)
I feel lack in App performance, Zooming and Panning is not smooth as the dynamic library gave by Mapbox guys.
I am still exploring. lemme know if you wanna me to check/explore.
Please accept this answer, if it is helpful.
Thank you,
Happy Coding.
Try swift 3
//determined is Point inner to realm polygons
func determinedInPoligon(point:CLLocationCoordinate2D, poligon:[CLLocationCoordinate2D]) -> Bool {
var result:Bool = false
var j = poligon.count - 1
for i in 0 ..< poligon.count {
if (poligon[i].longitude < point.longitude && poligon[j].longitude >= point.longitude || poligon[j].longitude < point.longitude && poligon[i].longitude >= point.longitude) &&
(poligon[i].latitude + (point.longitude - poligon[i].longitude) / (poligon[j].longitude - poligon[i].longitude) * (poligon[j].latitude - poligon[i].latitude) < point.latitude) {
result = !result;
}
j = i;
}
return result
}
func squareFrom(location: CGPoint, radius: Double) -> CGRect {//return rect with CGPoint center and radius
let length = radius
return CGRect(x: Double(location.x - CGFloat(length / 2)), y:
Double(location.y - CGFloat(length / 2)), width: length, height: length)
}
function handle
func handleTap(_ gesture: UITapGestureRecognizer) {
// Get the CGPoint where the user tapped.
let spot = gesture.location(in: mapView)
let my_features = mapView.visibleFeatures(at: spot)
let strZoomValue = mapView.zoomLevel > 15 ? "6" : "4"
//the feature structur object value not equal annotation object
for value in my_features.enumerated() {// feature items
if value.element is MGLPointAnnotation {
for annot in (mapView.annotations?.enumerated())! { // annotation items
if annot.element is MGLPointAnnotation {
//rounded lat and lng value
var arr_cllocation = [CLLocationCoordinate2D]()
for cllocation in [(annot.element as! MGLPointAnnotation).coordinate, (value.element as! MGLPointAnnotation).coordinate] {
let strLat = String(format: "%."+strZoomValue+"f", cllocation.latitude)
let strLon = String(format: "%."+strZoomValue+"f", cllocation.longitude)
arr_cllocation.append(
CLLocationCoordinate2D(latitude: CLLocationDegrees(strLat)!, longitude: CLLocationDegrees(strLon)!) )
}
if arr_cllocation.count == 2 &&
memcmp(&arr_cllocation[0], &arr_cllocation[1], MemoryLayout<CLLocationCoordinate2D>.size) == 0 {// 0 if equal object
// to do custom popup view
let instViewPopupLineClass = UINib(nibName: "ViewPopupBase", bundle: nil).instantiate(withOwner: self, options: nil).first as! UIView
for objectInst in instViewPopupLineClass.subviews.enumerated() {
if objectInst.element is UILabel {
let asdasdas = (annot.element as! MGLPointAnnotation).subtitle
(objectInst.element as! UILabel).text = asdasdas
MyCustomPopup(customView: instViewPopupLineClass, positionXY: spot)
break
}
}
}
}
} //for end
}
}
}
OR not accurate method, but a worker ;)
func handleTap(_ gesture: UITapGestureRecognizer) {// Get the CGPoint where the user tapped.
let spot = gesture.location(in: mapView)
let cllcordinatTap = mapView.convert(spot, toCoordinateFrom: mapView)
//for determined zoom scala
let deltScalaMap = abs(self.mapView.maximumZoomLevel - self.mapView.zoomLevel)
//The large is zoom maps, then smal size is tap location, and vice versa.
let checkScalaMap = deltScalaMap == 0 ? 1 : deltScalaMap
let _rect = squareFrom(location: CGPoint(x: cllcordinatTap.latitude, y: cllcordinatTap.longitude), radius: 0.00005 * checkScalaMap)
for annot in (mapView.annotations?.enumerated())! {
if annot.element is MGLPointAnnotation {
let _cordinatCurrentAnnotation = (annot.element as! MGLPointAnnotation).coordinate
if determinedInPoligon(point: _cordinatCurrentAnnotation, poligon:
[CLLocationCoordinate2D(latitude: CLLocationDegrees(_rect.minX), longitude: CLLocationDegrees(_rect.minY)),
CLLocationCoordinate2D(latitude: CLLocationDegrees(_rect.maxX), longitude: CLLocationDegrees(_rect.minY)),
CLLocationCoordinate2D(latitude: CLLocationDegrees(_rect.maxX), longitude: CLLocationDegrees(_rect.maxY)),
CLLocationCoordinate2D(latitude: CLLocationDegrees(_rect.minX), longitude: CLLocationDegrees(_rect.maxY))
]) {
// to do, if tap MGLPointAnnotation, annot.element
}
}
}
}
Here I got some workaround which helped to work with my requirement.
As per my need I am able to get single tap detection on both mapbox annotation marker and as well as on empty area of map
I created category for MGLMapView, (MGLMapView+EDCMapboxView) and overrides the touch methods
-touchesBegan:withEvent:
-touchesMoved:withEvent:
-touchesEnded:withEvent:
-touchesCancelled:withEvent:
MGLMapView+EDCMapboxView.h
#protocol EDCMapboxViewDelegate <NSObject>
#optional
- (void)mapboxViewDidCreateNewTicket:(MGLMapView*)mapView;
#end
#interface MGLMapView (EDCMapboxView)
#property (assign, nonatomic) BOOL shouldCreateNewTicket;
#property (weak, nonatomic) id <EDCMapboxViewDelegate> mapboxDelegate;
#end
MGLMapView+EDCMapboxView.m
#implementation MGLMapView (EDCMapboxView)
#dynamic mapboxDelegate;
#pragma mark -- Accessor
- (BOOL)shouldCreateNewTicket {
return [objc_getAssociatedObject(self, #selector(shouldCreateNewTicket)) boolValue];
}
- (void)setShouldCreateNewTicket:(BOOL)flag {
objc_setAssociatedObject(self, #selector(shouldCreateNewTicket), #(flag), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id<EDCMapboxViewDelegate>)mapboxDelegate{
return objc_getAssociatedObject(self, #selector(mapboxDelegate));
}
- (void)setMapboxDelegate:(id<EDCMapboxViewDelegate>)mapboxDelegate{
objc_setAssociatedObject(self, #selector(mapboxDelegate), mapboxDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -- Overrided method for UIResponder
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
NSLog(#"touchesBegan");
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
NSLog(#"touchesMoved");
self.shouldCreateNewTicket = NO;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
NSLog(#"touchesEnded");
}
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
NSLog(#"touchesCancelled");
[self createNewTicket];
}
- (void)createNewTicket{
if(self.shouldCreateNewTicket){
NSLog(#"Allowed to Create New ticket");
// Tells that tap is on empty area.
if([self.mapboxDelegate respondsToSelector:#selector(mapboxViewDidCreateNewTicket:)]){
[self.mapboxDelegate mapboxViewDidCreateNewTicket:self];
}
}
else{
NSLog(#"Not allowed to create New ticket");
// Tells tap is on annotation marker.
self.shouldCreateNewTicket = YES;
}
}
EDCMapboxViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.mapView.shouldCreateNewTicket = YES;
.....
.......
........
}
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation {
NSLog(#"annotationCanShowCallout");
// Tells that annotation is tapped, then do not allow to create ticket.
self.mapView.shouldCreateNewTicket = NO;
return YES;
}
- (void)mapboxViewDidCreateNewTicket:(MGLMapView*)mapView{
// Tells that tap is on empty area and not on marker, then allow to create ticket.
}
It worked for me, hope it will help you guys as well.
Thanks.
Here is how I solved it, you should get the concept.
func onMapSingleTapped(recognizer: UITapGestureRecognizer)
{
let viewLocation: CGPoint = recognizer.locationInView(map)
// check if any annotations are hit
if(map.annotations != nil)
{
for annotation in map.annotations!
{
if(annotation.isKindOfClass(MapCheckInAnnotation))
{
let pin: MapCheckInAnnotation = annotation as! MapCheckInAnnotation
if let pinView = pin.view
{
print("pinview \(pinView.frame.origin)")
// check if hit pin instead of just map
if(viewLocation.x >= pinView.frame.origin.x && viewLocation.x < pinView.frame.origin.x + pinView.frame.width)
{
if(viewLocation.y >= pinView.frame.origin.y && viewLocation.y < pinView.frame.origin.y + pinView.frame.height)
{
mapView(map, didSelectAnnotationView: pinView)
return
}
}
}
}
}
}
// nope, no annotations were clicked
let mapLocation: CLLocationCoordinate2D = map.convertPoint(viewLocation, toCoordinateFromView: map)
print("onMapSingleTapped \(mapLocation)")
}
I tried sample project for your question and it works well.
.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface ViewController : UIViewController<MKMapViewDelegate>
#property (strong, nonatomic) IBOutlet MKMapView *mapTapAnnotation;
#end
.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize mapTapAnnotation;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self setMapViewWithAnnnoationPin];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)setMapViewWithAnnnoationPin
{
mapTapAnnotation.showsUserLocation = YES;
mapTapAnnotation.mapType = MKMapTypeHybrid;
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(12.900988, 80.227930);
MKCoordinateSpan span = MKCoordinateSpanMake(0.005, 0.005);
MKCoordinateRegion region = {coord, span};
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
[annotation setCoordinate:coord];
[annotation setTitle:#"Single Tap"]; //You can set the subtitle too
[mapTapAnnotation setRegion:region];
[mapTapAnnotation addAnnotation:annotation];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(detectSingleTap)];
tapGesture.numberOfTapsRequired = 1;
[mapTapAnnotation addGestureRecognizer:tapGesture];
}
-(void)detectSingleTap
{
NSLog(#"Finded the single tap on map view");
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
NSLog(#"The single tap annotation pin point is - %ld",(long)view.tag);
}
The printed output results are
Finded the single tap on map view
The single tap annotation pin point is - 0
As I haven't been successful with the solutions above I implemented a small delay after the always recognized map tap event. If an annotation is selected in this small time frame, then nothing is called any further. Otherwise only the map view was tapped and no annotation.
Works pretty well in my case.
MapView part
protocol MapDelegate: class {
func didTapMapView()
}
class AppleMapView: MKMapView {
weak var mapDelegate: MapDelegate?
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if touches.first != nil {
mapDelegate?.didTapMapView()
}
}
........
}
MapViewController part
// property to store last tap at annotation
var lastAnnotationSelectionTime = Date()
// this method is called on every tap
func didTapMapView() {
let overlaySelectionDelayTime: TimeInterval = 0.2
DispatchQueue.main.asyncAfter(deadline: .now() + overlaySelectionDelayTime, execute: { [weak self] in
guard let self = self else { return }
if self.lastAnnotationSelectionTime.distance(to: Date()) > overlaySelectionDelayTime {
// only called if no annotation was tapped meanwhile
[ HANDLE TAP ON MAP HERE ]
}
})
}
// MARK: Marker Selection
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
lastAnnotationSelectionTime = Date() // setten if annotation is tapped
[ HANDLE TAP ON ANNOTATION HERE ]
}
I added a tap recognizer to a view:
UITapGestureRecognizer* tgr = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector( onTap )];
[view addGestureRecognizer: tgr];
The problem is that taps on subviews of view trigger onTap. How do I prevent that?
Suppose your parentView has a subView. You implement the following UIGestureRecognizerDelegate method, if the touch is inside the bounds of subView, you return no.
tgr.delegate = self;
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint locationInView = [touch locationInView:self.parentView];
if (CGRectContainsPoint(self.subView.frame, locationInView) ) {
return NO;
} else {
return YES;
}
}
Add subview to the background of the view, and attach tap gesture recogniser to the subview:
UIView* subview = [[UIView alloc] initWithFrame:view.bounds];
subview.backgroundColor = [UIColor clearColor];//or view.backgroundColor
[view addSubview:subview];
[view sendSubviewToBack:subview];
[subview addGestureRecognizer:tapRecognizer];
Swift 4.2 answer for an AlertView
Add the gesture recogniser and set the delegate:
let backgroundViewTapGesture = UITapGestureRecognizer(target: self, action: #selector(dismiss))
backgroundViewTapGesture.delegate = self
self.backgroundView.addGestureRecognizer(backgroundViewTapGesture)
self.backgroundViewTapGesture = backgroundViewTapGesture
Then add the extension to handle the tap
extension AlertView: UIGestureRecognizerDelegate {
// Only handle this for the background tap gesture
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
guard gestureRecognizer == backgroundViewTapGesture, let backgroundView = gestureRecognizer.view, let alertView = self.alertContentView else {
return true
}
let touchLocation = touch.location(in: backgroundView)
return !alertView.frame.contains(touchLocation)
}
}
In Swift:
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let locationInView = touch.location(in: mainView)
if debugOptionsView.isHidden {
return true
}
return !debugOptionsView.frame.contains(locationInView)
}
I've been trying to dismiss the modal form sheet view on outside tap on iOS 8 with no luck,
I've tried this code
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
But it doesn't detect outside view clicks, any suggestions ?
There are actually two problems in iOS 8. First, the gesture recognition does not begin.
I solved this by adding the UIGestureRecognizerDelegate protocol and implementing
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
{
return YES;
}
Also, don't forget to register the delegate with
recognizer.delegate = self;
Now the gesture recognizer should recognize gestures and the target method (handleTapBehind:) will be called.
Here comes the second problem in iOS 8: locationInView: doesn't seem to take the device orientation into account if nil is passed as a view. Instead, passing the root view works.
Here's my target code that seems to work for iOS 7.1 and 8.0:
if (sender.state == UIGestureRecognizerStateEnded) {
UIView *rootView = self.view.window.rootViewController.view;
CGPoint location = [sender locationInView:rootView];
if (![self.view pointInside:[self.view convertPoint:location fromView:rootView] withEvent:nil]) {
[self dismissViewControllerAnimated:YES completion:^{
[self.view.window removeGestureRecognizer:sender];
}];
}
}
In iOS 8, You can look at using the new UIPresentationController class. It gives you better control over the container around your custom view controller presentation (allowing you to correctly add a gesture recogniser of your own).
Here is a link to quite a simple tutorial as well: http://dativestudios.com/blog/2014/06/29/presentation-controllers/
Then add the dimming view tap-to-dismiss:
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[self.dimmingView addGestureRecognizer:singleFingerTap];
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
Swift 3.1 solution that works in both portrait and landscape.
class TapBehindModalViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if(self.tapOutsideRecognizer == nil) {
self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
self.tapOutsideRecognizer.numberOfTapsRequired = 1
self.tapOutsideRecognizer.cancelsTouchesInView = false
self.tapOutsideRecognizer.delegate = self
self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if(self.tapOutsideRecognizer != nil) {
self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
self.tapOutsideRecognizer = nil
}
}
func close(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
// MARK: - Gesture methods to dismiss this with tap outside
func handleTapBehind(sender: UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.ended) {
let location: CGPoint = sender.location(in: self.view)
if (!self.view.point(inside: location, with: nil)) {
self.view.window?.removeGestureRecognizer(sender)
self.close(sender: sender)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
I have a subview and a superview. The superview has an UITapGestureRecognizer attached to it.
UIView *superview = [[UIView alloc] initWithFrame:CGRectMake:(0, 0, 320, 480);
UIView *subview = [[UIView alloc] initWithFrame:CGRectMake:(100, 100, 100, 100);
UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget: self action: #selector(handleTap);
superview.userInteractionEnabled = YES;
subview.userInteractionEnabled = NO;
[superview addGestureRecognizer:recognizer];
[self addSubview:superview];
[superview addSubview:subview];
The recognizer is fired inside the subview as well, is there a way to exclude the recognizer from the subview?
I know this question has been asked before but I didn't find a good answer to it.
You can use gesture recognizer delegate to limit area where it can recognise touches similar to this example:
recognizer.delegate = self;
...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
CGPoint touchPoint = [touch locationInView:superview];
return !CGRectContainsPoint(subview.frame, touchPoint);
}
Note that you need to keep reference to your parent and child view (make them instance variables?) to be able to use them in delegate method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if(touch.view == yourSubview)
{
return NO;
}
else
{
return YES;
}
}
Thanks : https://stackoverflow.com/a/19603248/552488
For Swift 3, you can use view.contains(point) instead of CGRectContainsPoint.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if yourSubview.frame.contains(touch.location(in: view)) {
return false
}
return true
}