28

I've been searching for a way to disable vertical bounce for a UIScrollView but only at bottom. I still need to have the bounce at the top.

Couldn't find anything. Is this possible?

Thanks in advance!

sqreept
  • 5,236
  • 3
  • 21
  • 26
  • Related: [Disable UIScrollView bounce at the top of the screen](https://stackoverflow.com/questions/22736245/disable-uiscrollview-bounce-at-the-top-of-the-screen), by changing the scroll view's `bounces` propery in `scrollViewDidScroll` depending on its scroll position. – Alex Wally Sep 04 '18 at 06:29

7 Answers7

57

Use the UIScrollViewDelegate method scrollViewDidScroll to check the content offset of the scrollview and more or less check if the user is trying to scroll beyond the bottom bounds of the scroll view. Then just use this to set the scroll back to the end of the scroll view. It happens so rapidly that you can't even tell that the scroll view bounced or extended beyond its bounds at all.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height) {
        [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentSize.height - scrollView.frame.size.height)];
    }
}
Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
14

To disable vertical bounce at the top in Swift:

func scrollViewDidScroll(scrollView: UIScrollView) {
    if scrollView.contentOffset.y < 0 {
        scrollView.contentOffset.y = 0
    }
}

To disable vertical bounce at the bottom in Swift:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.height {
        scrollView.contentOffset.y = scrollView.contentSize.height - scrollView.bounds.height
    }
}
efremidze
  • 2,640
  • 1
  • 25
  • 35
  • @Blacky That would disable the bounce for the top and bottom, question asks for only one – efremidze Aug 01 '20 at 00:59
  • 1
    If you do not have it set up already, just a friendly reminder to add UIScrollViewDelegate and scrollView.delegate = self – Ben Feb 02 '21 at 03:10
4

Based on 0x7fffffff's answer I've created a class:

VerticalBounceControl.h

#import <UIKit/UIKit.h>

@interface VerticalBounceControl : NSObject

@property (nonatomic, assign) BOOL bounce;

- (id) initWithScrollView:(UIScrollView*)scrollView bounceAtTop:(BOOL)bounceAtTop bounceAtBottom:(BOOL)bounceAtBottom;

@end

VerticalBounceControl.m

#import "VerticalBounceControl.h"

@interface VerticalBounceControl ()
@property (nonatomic, retain) UIScrollView* scrollView;
@property (nonatomic, assign) BOOL bounceAtTop;
@property (nonatomic, assign) BOOL bounceAtBottom;
@end

@implementation VerticalBounceControl

- (id) initWithScrollView:(UIScrollView*)scrollView bounceAtTop:(BOOL)bounceAtTop bounceAtBottom:(BOOL)bounceAtBottom {
    self = [super init];
    if(self) {
        self.bounce = YES;
        self.scrollView = scrollView;
        self.bounceAtTop = bounceAtTop;
        self.bounceAtBottom = bounceAtBottom;
        [self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];
    }
    return self;
}

- (void) dealloc {
    [self.scrollView removeObserver:self forKeyPath:@"contentOffset"];
    self.scrollView = nil;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"contentOffset"]) {
        if (self.bounce && ((self.scrollView.contentOffset.y<=0 && self.bounceAtTop) || (self.scrollView.contentOffset.y>=self.scrollView.contentSize.height-1 && self.bounceAtBottom))) {
            self.scrollView.bounces = YES;
        } else {
            self.scrollView.bounces = NO;
        }
    }
}

@end

that can be used without the caller being a delegate of UIScrollView like this:

VerticalBounceControl* bounceControl = [[VerticalBounceControl alloc] initWithScrollView:anyScrollView bounceAtTop:YES bounceAtBottom:NO];

This way you can have this bounce behavior for UIScrollViews that you don't want to alter their delegate (like UIWebView's one).

Again, thanks go @0x7fffffff for the solution!

sqreept
  • 5,236
  • 3
  • 21
  • 26
  • A category of `UIScrollView` would have been a more elegant solution – Mark E Apr 05 '14 at 23:21
  • @MarkE What would a swift implementation involve? If not without the overriding of delegate methods. Curious. – Pavan Feb 28 '17 at 01:51
  • @Pavan I'm not very familiar with swift, I've not been on ios dev for a while. Although my comment was not swift related, categories are just a way to extend classes which I can't tell if is available on swift – Mark E Feb 28 '17 at 02:14
4

Swift 3:

This works to disable vertical bounce only at the bottom:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.height {
        scrollView.contentOffset.y = scrollView.contentSize.height - scrollView.bounds.height
    }
}
1

Let's disable the bounces when scroll to the bottom.

scrollView.bounces = scrollView.contentOffset.y < scrollView.contentSize.height - scrollView.frame.height
Tai Le
  • 8,530
  • 5
  • 41
  • 34
0

I added fabsf() to 0x7fffffff♦'s solution to also cater for negative(downward) scroll:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (fabsf(scrollView.contentOffset.y) >= scrollView.contentSize.height - scrollView.frame.size.height) {
        [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentSize.height - scrollView.frame.size.height)];
    }
}
MickeDG
  • 404
  • 5
  • 18
0

I have been looking a solution to disable bottom bounce only for a scrollview with wider contentSize width than device since I am using paging. None of those work for me but I found a way to do it. I thought I should share this since I wasted two days just to do this. I am using Swift 4 but this applies to objective c if you know how to convert this which should be easy if you have experience with both.

This works in a scrollview with device screen frame as frame.

  1. Add variable to be aware for scrollview's current page.
    var currentPage : Int!

  2. Set initial value
    currentPage = 0

  3. Update currentPage after scrolling
    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let width = scrollView.frame.size.width let page = (scrollView.contentOffset.x + (0.5 * width)) / width currentPage = Int(page) }

  4. Filter bottom scrolling and update contentOffset
    public func scrollViewDidScroll(_ scrollView: UIScrollView) { if CGFloat(currentPage) * screenWidth < scrollView.contentOffset.x || CGFloat(currentPage) * screenWidth < scrollView.contentOffset.y || (((CGFloat(currentPage) * screenWidth - scrollView.contentOffset.x) >= 0) && scrollView.contentOffset.y > 0){ scrollView.contentOffset.y = 0 } }

CGFloat(currentPage) * screenWidth < scrollView.contentOffset.x || CGFloat(currentPage) * screenWidth < scrollView.contentOffset.y will filter bottom left and right scrolling

(((CGFloat(currentPage) * screenWidth - scrollView.contentOffset.x) >= 0) && scrollView.contentOffset.y > 0) will filter bottom mid scrolling

scrollView.contentOffset.y = 0 will stop it from scrolling

726a
  • 89
  • 9