问题
collectionView didn't call didSelectItemAtIndexPath when superview has tapGesture。is why?
why it print "doGesture" according to the Responder Chain?
- initCollectionView then add to self.view
- addTapGesture in self.view
- click item in iPhone.
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; }
回答1:
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.
回答2:
if "doGesture" appears in the log, then it is working as expected since you added the tap recognizer on top of your entire view. if you want the tap to be recognized AND didSelect to be called, you need to call didSelect yourself from your tap code.
let locationInView: CGPoint = tapGesture.location(in: self.view)
let locationInCollectionView: CGPoint = tapGesture.location(in: self.collectionView)
if let indexPath: IndexPath = collectionView.indexPathForItem(at: locationInCollectionView) {
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredVertically)
}
回答3:
Set tapGesture.cancelsTouchesInView = NO
This allows touches inside other views to go through, such as collectionView didSelectItemAtIndexPath
Note that you will also get the tapGesture event. If you want to ignore that when a collectionViewCell is tapped, add this delegate method:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
CGPoint touchPoint = [touch locationInView:self.collectionView];
return ![self.collectionView hitTest:touchPoint withEvent:nil];
}
来源:https://stackoverflow.com/questions/42372609/collectionview-didnt-call-didselectitematindexpath-when-superview-has-gesture