Limiting the scrollable area in UIScrollView

2019-01-11 15:35发布

问题:

I have a UIScrollView that is scrolling a fairly large UIView.

At certain times I want to limit the area the user can scroll around in. For example, I may only want to allow them to view the bottom quarter of the view.

I am able to limit the area by overriding scrollViewDidScroll and then calling setContentOffset if the view has scrolled too far. But this way I can't get it bounce back as smoothly as the UIScrollView can naturally do when scrolling beyond the bounds of the UIView.

Is there a better way to limit the scrollable area in a UIScrollView?

回答1:

I would change the contentSize property of the scroll view to the size of the area you want the user to be able to scroll around in and adjust the frame.origin of the subview such the upper left boundary you want appears at (0, 0) relative to the scroll view. For example, if your view is 800 points tall and you want to show the bottom quarter, set the height of contentSize to 200 and set the y component of view.frame.origin to -600.



回答2:

I've found something that works for me. It let's you scroll to point 0,0 but no further:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView.contentOffset.x <= -1) {
        [scrollView setScrollEnabled:NO];
        [self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
        [scrollView setScrollEnabled:YES];
    }
}

You could do the same for top, bottom or right (x or y)



回答3:

Another approach is to override the UIScrollView's method:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event.

Returning YES will allow the user to scroll. Returning NO will not.

NOTE: This will disable all touches to any views imbedded inside the UIScrollView that pointInside returns NO to. Useful if the area you don't want to scroll from doesn't have any interaction.


This example only allows the UIScrollView to scroll when the user is scrolling over a UITableView. (A UITableView and two UIViews are imbedded inside the UIScrollView)

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *subview in self.subviews) {
        if ([subview pointInside:[self convertPoint:point toView:subview] withEvent:event] && ![subview isKindOfClass:[UITableView class]]) {
            return NO;
        }
    }
    return YES;
}


回答4:

a small improvement on Yoko's answer in Swift 4 will be

override func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.y > 600 {
        let anim = UIViewPropertyAnimator(duration: 1, dampingRatio: 0.5) {
            scrollView.isScrollEnabled = false
            scrollView.setContentOffset(CGPoint(x: 0, y: 600), animated: false)
            scrollView.isScrollEnabled = true
        }
        anim.startAnimation()
    }
}

which will make the scrollview animate really similar to what its supposed to do. The slower drag when you are in the "bounce" area will not work and animation duration has to depend on the distance (not constant like here) if you want to be exact. You can also try to do this logic in scrollViewDidScroll and see how it differs. The key thing is that setContentOffset(_:,animated:) has to be with animated: false so that the UIViewPropertyAnimator's block can capture it and animate it