As mentioned in other answers, you can only attach a gesture recognizer to one view.
You could always have the UICollectionView add the gesture recognizer to the collection view cell in cellForItemAtIndexPath:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCustomCollectionViewCellIdentifier forIndexPath:indexPath];
if (cell.contentView.gestureRecognizers.count == 0) {
[cell.contentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(myViewWasTapped:)]];
}
return cell;
}
If you go with this approach, you will have to make sure that you do not continuously add gesture recognizers to the same cell if the cell is reused. You can check the view's gestureRecognizers
property to see if the cell's contentView already has a gesture recognizer.
When the cell is tapped, you can then access the view that was tapped through the view
property of the gestureRecognizer passed in to your method myViewWasTapped:(UIGestureRecognizer *)sender
.
Another way to do this is with delegation, a popular method of passing information around in iOS. If you subclass UICollectionViewCell, you can declare a protocol for the "delegate" of the cell. This is, basically, just a list of all the methods that an object should implement if it wants to be the delegate of the cell. In you UICollectionViewCell h file, you can declare the protocol in the following way.
@class CustomCardCollectionViewCell;
@protocol CustomCollectionViewCellDelegate <NSObject>
@required
- (void)customCollectionViewCellWasTapped:(CustomCollectionViewCell *)cell;
@end
Then in your cell's interface you would declare a property called delegate (although you can call it whatever you want). Delegate is of type id
which conforms to the protocol CustomCollectionViewCellDelegate
or whatever you decide to call your protocol.
@interface CustomCollectionViewCell : UICollectionViewCell
@property (weak, nonatomic) id<CustomCollectionViewCellDelegate> delegate;
//Other interface declaration stuffs.
@end
Importantly, the UICollectionView will have to set the delegate property of the cell to be itself when it creates the cell. You can think of this as a back pointer (which is why the delegate property is declared weak) to the UICollectionView from the CustomCollectionViewCell.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCustomCollectionViewCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
return cell;
}
The benefit of delegation is that the delegate doesn't have to be any particular type of object, except one that conforms to the protocol that you defined. Therefore, you don't need the custom cell to be aware of any other classes. Hooray!
You also need to remember to have the cell call the method on it's delegate when the cell's gesture recognizer detects a tap.
[self.delegate customCollectionViewCellWasTapped:self];
FOLLOW UP QUESTION
If you need to detect that a particular subview of the cell was tapped, there are a few ways of solving that problem. One way would be to expose the subview as a public property (readonly perhaps) on your subclass of UICollectionViewCell and attach the UIGestureRecognizer directly to that subview so that it would only fire when the subview was tapped. Let's say that subview is a star button indicating a favorite. This could then be accomplished in the following way.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCustomCollectionViewCellIdentifier forIndexPath:indexPath];
if (cell.contentView.gestureRecognizers.count == 0) {
[cell.starView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(starWasTapped:)]];
}
return cell;
}
You could, alternatively, add a delegate method to your delegate protocol. Something like
- (void)customCollectionCell:(CustomCollectionViewCell *)cell starViewWasTapped:(UIView *)starView
If you go with this method, you could either have the CustomCollectionViewCell
attach it's gesture recognizer directly to the star view, or attach the gesture recognizer to the contentView of the cell and calculate whether the gesture recognizer's locationInView falls within the frame of the starView.
You should avoid adding the gesture recognizer to the content view and having the UICollectionView calculate the location of the touch within the cell to determine if the star was tapped. The reason is that this would require the UICollectionView to have knowledge of the location of the star within the contentView of the cell. Each view should be responsible for the location of it's subviews.
If the star was a UIButton, you could forgo the UIGestureRecognizer entirely by setting the tag property on the button and having the UICollectionView add it's self as a target on the button. You can then tell which button in which cell was tapped by checking the tag property of the button that is sent with the action.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCustomCollectionViewCellIdentifier forIndexPath:indexPath];
[cell.starButton addTarget:self action:@selector(starButtonWasTapped:) forControlEvents:UIControlEventTouchUpInside];
cell.starButton.tag = indexPath.row;
return cell;
}
- (void)starButtonWasTapped:(UIButton *)starButton {
//Do something based off of the tag of the button.
}