22

I have a UICollectionView that horizontally scrolls to show one UICollectionViewCell at a time. Each UICollectionViewCell the has a vertically scrolling UIScrollView as subview for scrolling the contents of the cell. It is only 90 % or so of the inner part of the UICollectionViewCell that is covered by the UIScrollView - i.e. the outer frame of the cell is not covered by this.

It turns out that the part of the UICollectionViewCell that is covered by the UIScrollViewcancels the UICollectionView delegate didSelectItemAtIndexPath. Thus when a simple tap happens within the UIScrollView this method is not invoked, whereas if the tap happens on the outer part of the cell, i.e. outside the UIScrollView, this method is invoked.

Any suggestions as to how to achieve a setup where it is possible to invoke the didSelectItemAtIndexPath method even when the tap happens within the UIScrollView?

Zappel
  • 1,612
  • 1
  • 22
  • 37

4 Answers4

37

I found that the most effective approach is to steal the panGestureRecognizer, exposed by UIScrollView and disable userInteraction on the scrollView. That way, you get the behavior of the scrollview but maintain the interaction on the collection view. On your UICollectionViewCell subclass:

self.scrollView.userInteractionEnabled = NO;
[self.contentView addGestureRecognizer:self.scrollView.panGestureRecognizer];

This is a method Apple recommends and demonstrates in WWDC 2014 session 235 (Advanced Scrollviews and Touch Handling Techniques)

Rishil Patel
  • 1,977
  • 3
  • 14
  • 30
Mikkel Selsøe
  • 1,171
  • 1
  • 11
  • 23
6

Here's a UIScrollView subclass approach that maintains cell selection functionality, and also allows for UIControl selection (buttons, etc) both in & outside of the scrollview.

Swift 3

class CellContentScrollView: UIScrollView {

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let hitTargetView = super.hitTest(point, with: event)
    return hitTargetView as? UIControl ?? (hitTargetView == self ? nil : superview)
  }

  override func didMoveToSuperview() {
    superview?.addGestureRecognizer(panGestureRecognizer)
  }

}

Swift 2

class CellContentScrollView: UIScrollView {

    // MARK: - UIView override

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let hitTargetView = super.hitTest(point, withEvent: event)
        return hitTargetView as? UIControl ?? (hitTargetView == self ? nil : superview)
    }

    override func didMoveToSuperview() {
        superview?.addGestureRecognizer(panGestureRecognizer)
    }
}

Objective-C

@implementation CellContentScrollView

    #pragma mark - UIView override

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *hitTargetView = [super hitTest:point withEvent:event];

        if ([hitTargetView isKindOfClass:UIControl.class]) {
            return hitTargetView;
        } else if (hitTargetView != self) {
            return self.superview;
        }

        return nil;
    }

    - (void)didMoveToSuperview {
        [self.superview addGestureRecognizer:self.panGestureRecognizer];
    }

@end
Chris
  • 39,719
  • 45
  • 189
  • 235
zath
  • 1,044
  • 8
  • 13
1

The tap on the UIScrollView is used to see whether scrolling should be done.

You should catch the single tap on the UIScrollView itself and pass it on to the surrounding UICollectionViewCell.

Community
  • 1
  • 1
Jacco
  • 3,251
  • 1
  • 19
  • 29
  • Thanks! I found this answer a tiny bit better: http://stackoverflow.com/a/5216518/746968 – Zappel Jan 13 '13 at 11:01
  • 1
    @Zappel How then would you forward the tap to the UICollectionView to emulate an interactive selection tap that also fires the UICollectionView's selection delegate methods? – lhunath Mar 26 '14 at 11:47
  • 8
    -1 You should definetly explain how you would "pass it on to the surrounding UICollectionViewCell" – marchinram May 17 '14 at 00:37
0

Add tap gesture to your super view

override func viewDidLoad() {
        super.viewDidLoad()
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView(gesture:)))
        view.addGestureRecognizer(tapGesture)
    }

@objc func didTapView(gesture: UITapGestureRecognizer) {
    view.endEditing(true)
    let touchLocation:CGPoint = gesture.location(ofTouch: 0, in: self.collectionView)
    let indexPath = self.collectionView.indexPathForItem(at: touchLocation)
    if indexPath != nil {
        let cell = self.collectionView.cellForItem(at: indexPath!)
        if (cell?.isSelected)! {
            //PREFORM DESELECT
        } else {
            //PREFORM SELECT HERE
        }
    }
} 
Tanvir Singh
  • 131
  • 1
  • 2