What is the correct way to implement a scrollview

2019-09-05 15:16发布

问题:

This question is a spin-off of Sticky scrollview subview has incorrect frame after scrolling fast. Compilable example. If you would like to download the sample project, visit that link.

The scenario: (though this will vary)

ViewController
.    View
.    .    MainScrollview
.    .    .     ImageScroller
.    .    .     .     ImageView(s)
.    .    .     TextView (Inside container view)

The main scrollview is the size of the screen. The top ~half of the screen is a horizontally scrolling scrollview of UIImageViews.

The goal: Scrolling up (by dragging down) past the normal offset will stretch the visible image in the imageScroller (This is completed in the example). Scrolling down (by dragging up) past 0 offset will have the textView scroll above the imagesScroller, making the imagesScroller appear to be static.

My implementation will work (with the answer given in the linked question) but it is clearly not optimal. I've tried to rearrange the imageScroller behind the mainScrollview so it actually is static, but that requires making a transparent view on the mainScroller the size of the imageScroller that will pass down touch events to the imageScroller. To me, that's no cleaner than constantly calculating the proper Y-offset for the imageScroller in the scheme described above.

回答1:

I suggest you should do this by changing things around a little.

1 - Rearrange your view hierarchy

     self.view ---+
                  |
                  +---> self.mainScrollView
                  |
                  +---> self.imageScrollView

in your terms...

    ViewController
    .    View
    .    .    MainScrollview
    .    .    .     TextView (Inside container view)
    .    .    ImageScroller
    .    .    .    ImageView(s)

Your imageScrollView is a sibling of mainScrollView, not a child.

self.mainScrollView is the uppermost view and completely covers imageScrollView. In both cases their frame is self.view.bounds. mainScrollView should have a transparent background ([UIColor clearColor]) so that you can see the imageScrollView behind it. The mainScrollView's textView has it's origin set to self.view.bounds.size.height so that you don't see it until you start scrolling down.

2 - Manage your gestures

As mainScrollView is uppermost, it's PanGestureRecognizer will capture all of your touches, and nothing will get forwarded to imageScrollView. You need to fix this so that you can scroll the images.

To manage gesture negotiation, you can attach both of these gestures onto a single view. The simplest implentation is to attach the imageScrollView's panGestureRecognizer to the mainScrollView:

    UIGestureRecognizer* panGR = self.imageScroller.panGestureRecognizer;
    [self.mainScrollView addGestureRecognizer:panGR];

imageScrollview.panGestureRecognizer will now recognise gestures on mainScrollView, and will continue to pass resulting messages through to it's imageScrollView.

This may be all you need to do. In my test implementation this is sufficient, so long as at least one of mainViewController's canCancelContentTouches or delaysContentTouches is set to YES.

For more fine-grained control you could make a transparent gesture view overlay on both scrollViews, and attach both scrollView's panGestureRecognizers to the gesture view. Take care with this - the order in which you attach the gestureRecognizers will be reflected in the order in which gestures are intercepted.

For more detail on hijacking scrollview's panGestureRecognizer, it's well worth watching "Enhancing User Experience with Scroll Views" from WWDC 2012, especially the second half, where a scrollView is used to control a sibling openGL view.

As regards stretching the image when dragging down, this might be done effectively via the scrollViewDidScroll delegate method on mainScrollView - apply a transform on the imageView while contentOffset.y is negative.