iosuicollectionviewuiscrollviewuigesturerecognizeruicollectionviewflowlayout

UICollectionView consuming vertical pan gesture


I have a UICollectionView that is inside a UITableView and that is inside a UIScrollView. The collection view is a horizontally scrolling carousel of items and this works fine if there are enough items in the collection view so that it is wider than the width of the screen. But the problem I am encountering is that if there is only one or two items (not enough to fill the width of the screen), then the collection view seems to consume the vertical pan gesture and does not let the scroll view scroll.

This is the way my app is laid out currently. There is the scroll view (green in the following picture), which contains all the contents on the page. Inside that, there is a table view which has multiple sections. Each section has a section header, which is tappable. Upon tapping the section header, the section expands by adding one row which contains a collection view. The collection view is a horizontally scrolling flow layout collection view.

enter image description here

This works fine as long as there are enough collection view cells in the collection view that it goes off the screen (as shown in the previous picture. But if there is only one item, for example, (as is shown in the next picture), then the scroll view cannot be scrolled vertically if a pan gesture starts anywhere inside the collection view (anywhere in the blue region or tan region in the following picture).

enter image description here

Code

Here is the code to initialize the collection view:

- (UICollectionView *)collectionView {
    if (!self->_collectionView) {
        UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
        layout.minimumInteritemSpacing = 10;
        layout.minimumLineSpacing = 10;
        
        layout.sectionInset = UIEdgeInsetsMake(0, 16, 0, 16);
        layout.sectionInsetReference = UICollectionViewFlowLayoutSectionInsetFromContentInset;
        layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
        
        self->_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
        self->_collectionView.translatesAutoresizingMaskIntoConstraints = NO;
        self->_collectionView.backgroundColor = nil;
        self->_collectionView.delegate = self;
        self->_collectionView.dataSource = self;
        
        [self.collectionView registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:@"MyCollectionViewCell"];
    }
    return self->_collectionView;
}

Question

Does anyone know why the collection view would be consuming the vertical pan gesture when there are only one or two items or how to fix it?


Solution

  • All of the three views are actually scroll views. UITableView and UICollectionView classes inherit from UIScrollView.

    UIScrollView recognizes scrolling via its panGestureRecognizer.

    Since you don’t define the dependencies between those recognizers among your scroll views, the touch event is consumed in the following way:

    And this cascaded pan processing goes up the view hierarchy like that until the gesture is processed or there is no more scroll views.

    Seems like in your situation the pan recognizer of the collection view with horizontal scroll does not fail for vertical scroll when the content size of the collection view is less than or equal to its bounds size.

    You can try two things about this. Either require failure of the scroll view pan recognizer for collection view pan recognizer

    [self->_collectionView.panGestureRecognizer requireGestureRecognizerToFail:scrollView.panGestureRecognizer]
    

    or disable scrolling of collection view when it has content size less than bounds. For that, you can create a subclass of UICollectionView, override setContentSize: and setBounds: methods and assign scrollEnabled accordingly. Then use your subclass instead of the vanilla UICollectionView.