Search code examples
iosobjective-cuicollectionviewuitapgesturerecognizer

collectionView didn't call didSelectItemAtIndexPath when superview has gesture


collectionView didn't call didSelectItemAtIndexPath when superview has tapGesture。is why?
why it print "doGesture" according to the Responder Chain?

  1. initCollectionView then add to self.view
  2. addTapGesture in self.view
  3. click item in iPhone.
  4. not call didSelectItemAtIndexPath.

    - (void)viewDidLoad {
        [super viewDidLoad];
        UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
        self.collectionView = [[MyCollectionView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 100) collectionViewLayout:flowLayout];
        [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"abc"];
        self.collectionView.delegate = self;
        self.collectionView.dataSource = self;
        [self.view addSubview:self.collectionView];
    
        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doGesture)];
        tapGesture.delegate = self;
        [self.view addGestureRecognizer:tapGesture];
    }
    
    - (void)doGesture
    {
        NSLog(@"%@",@"doGesture");
    }
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
    {
        return 100;
    }
    
    - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
    {
        NSLog(@"%@",@"didSelectItemAtIndexPath");
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"abc" forIndexPath:indexPath];
        if (indexPath.row %2==0) {
            cell.backgroundColor = [UIColor redColor];
        } else{
            cell.backgroundColor = [UIColor grayColor];
        }
        return cell;
    }
    

Solution

  • You need to set tapGesture.cancelsTouchesInView = NO.

    Depending on Your logic You may want to check out delaysTouchesBegan too.

    From Apple docs:

    When the value of this property is false (the default), views analyze touch events in began and moved in parallel with the receiver. When the value of the property is true, the window suspends delivery of touch objects in the UITouchPhaseBegan phase to the view. If the gesture recognizer subsequently recognizes its gesture, these touch objects are discarded. If the gesture recognizer, however, does not recognize its gesture, the window delivers these objects to the view in a touchesBegan(:with:) message (and possibly a follow-up touchesMoved(:with:) message to inform it of the touches’ current locations). Set this property to true to prevent views from processing any touches in the UITouchPhaseBegan phase that may be recognized as part of this gesture.

    EDIT : For completeness I am adding code snippet for filtering the gesture recognizer's handling, when the user taps in on the collection view. My approach is different from the one mentioned in @DonMag's answer.

    - (void)doGesture:(UIGestureRecognizer*) sender
    {    
        CGPoint locationInView = [sender locationOfTouch:0 inView:self.view];
        CGPojnt convertedLocation = [self.collectionView convertPoint:location fromView:self.view];
    
        // from Apple doc
        // Returns a Boolean value indicating whether the receiver contains the specified point.
        if (![self.collectionView pointInside:convertedLocation withEvent:nil])
        {
          NSLog(@"%@",@"doGesture");        
        }
    }
    

    EDIT 2: Maybe the clearest explanation about gesture recognizers and how they work, when added in views:

    Every gesture recognizer is associated with one view. By contrast, a view can have multiple gesture recognizers, because a single view might respond to many different gestures. For a gesture recognizer to recognize touches that occur in a particular view, you must attach the gesture recognizer to that view. When a user touches that view, the gesture recognizer receives a message that a touch occurred before the view object does. As a result, the gesture recognizer can respond to touches on behalf of the view.