1

I am adding a uipickerview as the subview of the main view. To dismiss the pickerview on tapping the backgroud view, i am adding a UITapGestureRecognizer to the main view.

I am using the following code to add the GestureRecognizer for main view

 UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    gestureRecognizer.numberOfTapsRequired=1;
    gestureRecognizer.numberOfTouchesRequired=1;
    gestureRecognizer.delegate = self;
    [self.view addGestureRecognizer:gestureRecognizer];
    [gestureRecognizer release];

In the handleSingleTap method i am dismissing the pickerview.

But the problem is handleSingleTap is also called when I tap inside the pickerview. To avoid it i used the following delegate method of UIGestureRecognizer

 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    /*
     *If the tap is inside a button return NO, to ensure the button click is detected.
     */
    if ([touch.view isKindOfClass:[UIButton class]]){
        return FALSE;
    }else if([touch.view isKindOfClass:[UIPickerView class]]) {

        return FALSE;

    }
    return TRUE;
}

It is working for button,But is not working for UIPickerView. Can anyone help me with this?

PgmFreek
  • 6,374
  • 3
  • 36
  • 47

4 Answers4

1

I implemented the following in Swift:

    override func viewDidLoad()
        {
            super.viewDidLoad()
            let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
            tap.cancelsTouchesInView = false 
            view.addGestureRecognizer(tap)
        }

        func handleTapGesture(sender: AnyObject)
        {
            let subview = view?.hitTest(sender.locationInView(view), withEvent: nil)

            if(!(subview?.isDescendantOfView(timePicker) ?? false))
            {//might want to add a condition to make sure it's not your button ^^
                showTimePicker(false)//method which handles showing/hiding my picker 
            }
         }
Tom Howard
  • 4,672
  • 2
  • 43
  • 48
1

It is possible that the view touched (touch.view) is one of the subviews of the pickerview. I'd try testing:

[[pickerview subviews] containsObject: touch.view];
Peter Sarnowski
  • 11,900
  • 5
  • 36
  • 33
  • When i tapped inside the pickerview cell, the issue still exists – PgmFreek Jan 18 '12 at 07:40
  • yes..but when i tapped inside pickerview other than cell it is working fine. – PgmFreek Jan 18 '12 at 07:42
  • Then perhaps consider another approach: instead of using gesture recognizer, change your background view to UIControl class and associate an action with it's TouchUpInside control event. Have this action dismiss the view - your button and uipicker can be subviews of the UIControl. – Peter Sarnowski Jan 18 '12 at 07:47
1

I have coded up a solution to your particular requirement.

first, i implemented your code as you have described and observed the same problem you reported - spurious tap events being sent to tap handler, when you tapped on anything, including a UIButton.

this told me that the UITapGestureRecogniser was "stealing" the touches that should have gone to the UIButton, so i decided the simplest, most pragmatic solution was to use that feature to my advantage, and so i assigned a UITapGestureRecogniser to both the pickerview and the button also. the taps for the pickerview we just discard, the others we parse and pass on to the button's tap handler.

note - for expedience i assigned the pickerview's datasource and delegate in the xib. you will need to do that also, or set it in code.

header

//
//  ViewController.h
//  stackExchangeDemo
//
//  Created by unsynchronized on 18/01/12.
//  released to public domain via http://stackoverflow.com/a/8908028/830899
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController 
<UIPickerViewDelegate, UIPickerViewDataSource>

{
   UIButton *btn1; 
   UIPickerView *picker1;
}

@property (retain, nonatomic) IBOutlet UIButton *btn1;
@property (retain, nonatomic) IBOutlet UIPickerView *picker1;

@end

implementation

//
//  ViewController.m
//  stackExchangeDemo
//
//  Created by unsynchronized on 18/01/12.
//  released to public domain via http://stackoverflow.com/a/8908028/830899
//

#import "ViewController.h"

@implementation ViewController
@synthesize btn1;
@synthesize picker1;

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

-(void) handleSingleTap:(UITapGestureRecognizer *) tapper {

    if (tapper.state == UIGestureRecognizerStateEnded) {
        NSLog(@"%@",NSStringFromSelector(_cmd));
    }

}

- (IBAction)handleButtonTap:(id)sender {
    NSLog(@"%@",NSStringFromSelector(_cmd));
}


-(void) handleButtonTapGesture:(UITapGestureRecognizer *) tapper {
    // call the buttons event handler

    UIControlEvents eventsToHandle = UIControlEventTouchUpInside;

    if (tapper.state == UIGestureRecognizerStateEnded) {
        UIButton *btn = (UIButton *) tapper.view;

        for (NSString *selName in [btn  actionsForTarget:self forControlEvent:eventsToHandle]) {

            SEL action = NSSelectorFromString(selName);
            if (action) {

                [self  performSelector:action withObject:btn1];
                break;
            }

        };  


    }

}

-(void) handleDummyTap:(UITapGestureRecognizer *) tapper {
    // silently ignore the tap event for this view.
}

-(void) setupTap:(UIView *) view action:(SEL)action {
    // assign custom tap event handler for given view.
    UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:action];
    [view addGestureRecognizer:gestureRecognizer];
    [gestureRecognizer release];    
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self setupTap:self.view action:@selector(handleSingleTap:)];
    [self setupTap:picker1 action:@selector(handleDummyTap:)];
    [self setupTap:btn1 action:@selector(handleButtonTapGesture:)];



}



- (void)viewDidUnload
{
    [self setBtn1:nil];
    [self setPicker1:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}


#pragma mark @protocol UIPickerViewDataSource<NSObject>

// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
    return 1;
}

// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {

    return 1;
}


#pragma mark @protocol UIPickerViewDelegate<NSObject>
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
    return [@"so long and thanks for all the fish".copy autorelease ];
}


- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    NSLog(@"%@",NSStringFromSelector(_cmd));

}

- (void)dealloc {
    [btn1 release];
    [picker1 release];
    [super dealloc];
}
@end
unsynchronized
  • 4,828
  • 2
  • 31
  • 43
  • note - this example has the default stubs that XCode 4.2 creates automatically, relevant code is up to and including viewDidLoad. (picker view delegate stuff is at the end, inside #pragma marks) – unsynchronized Jan 18 '12 at 09:57
  • also note - i take it you are already able to "dismiss" the pickerview, and the issue you were having is with false triggers. this example does not dismiss the pickerview but simply calls NSLog. to dismiss the pickerview, you can either use picker1.hidden = YES; or remove [picker1 removeFromSuperview]; (in which case you would need to deal with adding it back again later) – unsynchronized Jan 18 '12 at 10:03
0

I simply added an invisible UIControl instance behind the UIPickerView, which covers all the window, and gets all the touches behind UIPickerView. If it is touched, then both the UIPickerView and the UIControl is dismissed. (SelectButton and CancelButton are accessory buttons to UIPickerView.)

@property (strong, nonatomic) UIControl *touchRecognizer;

- (IBAction)showPicker:(id)sender {

    self.touchRecognizer = [[UIControl alloc]initWithFrame:self.view.window.bounds];
    [self.view.window addSubview:self.touchRecognizer];
    [self.touchRecognizer addTarget:self action:@selector(touchedOutsidePicker:) forControlEvents:UIControlEventTouchUpInside];

    [self.textField becomeFirstResponder];
}

- (IBAction)touchedOutsidePicker:(id)sender {
    [self.touchRecognizer removeFromSuperview];
    self.touchRecognizer = nil;

    [self.textField resignFirstResponder];
}

-(void)selectButtonPressed:(id)sender{
    [self.touchRecognizer removeFromSuperview];
    self.touchRecognizer = nil;

    [self.textField resignFirstResponder];

}

-(void)cancelButtonPressed:(id)sender{
    [self.touchRecognizer removeFromSuperview];
    self.touchRecognizer = nil;

    [self.textField resignFirstResponder];
}
SPQR3
  • 647
  • 7
  • 20