UIScrollView - showing the scroll bar

IphoneObjective CUiscrollviewScrollbar

Iphone Problem Overview


Possibly a simple one!

Does anyone know how to get the scroll bar of a UIScrollView to constantly show?

It displays when the user is scrolling, so they can see what position of the scroll view they are in.

BUT I would like it to constantly show because it is not immediately obvious to the user that scrolling is available

Any advice would be highly appreciated.

Iphone Solutions


Solution 1 - Iphone

No, you can't make them always show, but you can make them temporarily flash.

[myScrollView flashScrollIndicators];

They are scroll indicators, not scroll bars. You can't use them to scroll.

Solution 2 - Iphone

my solution for show scroll indicators all the time

#define noDisableVerticalScrollTag 836913
#define noDisableHorizontalScrollTag 836914

@implementation UIImageView (ForScrollView)

- (void) setAlpha:(float)alpha {

if (self.superview.tag == noDisableVerticalScrollTag) {
    if (alpha == 0 && self.autoresizingMask == UIViewAutoresizingFlexibleLeftMargin) {
        if (self.frame.size.width < 10 && self.frame.size.height > self.frame.size.width) {
            UIScrollView *sc = (UIScrollView*)self.superview;
            if (sc.frame.size.height < sc.contentSize.height) {
                return;
            }
        }
    }
}

if (self.superview.tag == noDisableHorizontalScrollTag) {
    if (alpha == 0 && self.autoresizingMask == UIViewAutoresizingFlexibleTopMargin) {
        if (self.frame.size.height < 10 && self.frame.size.height < self.frame.size.width) {
            UIScrollView *sc = (UIScrollView*)self.superview;
            if (sc.frame.size.width < sc.contentSize.width) {
                return;
            }
        }
    }
}

[super setAlpha:alpha];
}
@end

UPDATE: This solution cause some issues on 64-bit. For more detail look here

Solution 3 - Iphone

As far as I know, this isn't possible. The only API call which controls displaying the scroll indicator is showsVerticalScrollIndicator and that can only disable displaying the indicator altogether.

You could flashScrollIndicators when the view appears so that the user knows where in the scroll view they are.

Solution 4 - Iphone

This one worked for me:

#define noDisableVerticalScrollTag 836913
#define noDisableHorizontalScrollTag 836914

@implementation UIImageView (ForScrollView) 

- (void) setAlpha:(float)alpha {
    
    if (self.superview.tag == noDisableVerticalScrollTag) {
        if (alpha == 0 && self.autoresizingMask == UIViewAutoresizingFlexibleLeftMargin) {
            if (self.frame.size.width < 10 && self.frame.size.height > self.frame.size.width) {
                UIScrollView *sc = (UIScrollView*)self.superview;
                if (sc.frame.size.height < sc.contentSize.height) {
                    return;
                }
            }
        }
    }
    
    if (self.superview.tag == noDisableHorizontalScrollTag) {
        if (alpha == 0 && self.autoresizingMask == UIViewAutoresizingFlexibleTopMargin) {
            if (self.frame.size.height < 10 && self.frame.size.height < self.frame.size.width) {
                UIScrollView *sc = (UIScrollView*)self.superview;
                if (sc.frame.size.width < sc.contentSize.width) {
                    return;
                }
            }
        }
    }
    
    [super setAlpha:alpha];
}
@end

I got this snippet from here: http://www.developers-life.com/scrollview-with-scrolls-indicators-which-are-shown-all-the-time.html

Solution 5 - Iphone

Swift 3+

  1. Timer

    var timerForShowScrollIndicator: Timer?

  2. Methods

    /// Show always scroll indicator in table view func showScrollIndicatorsInContacts() { UIView.animate(withDuration: 0.001) { self.tableView.flashScrollIndicators() } }

    /// Start timer for always show scroll indicator in table view func startTimerForShowScrollIndicator() { self.timerForShowScrollIndicator = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(self.showScrollIndicatorsInContacts), userInfo: nil, repeats: true) }

    /// Stop timer for always show scroll indicator in table view func stopTimerForShowScrollIndicator() { self.timerForShowScrollIndicator?.invalidate() self.timerForShowScrollIndicator = nil }

  3. Use

startTimerForShowScrollIndicator in viewDidAppear

stopTimerForShowScrollIndicator in viewDidDisappear

Solution 6 - Iphone

I want to offer my solution. I don't like the most popular variant with category (overriding methods in category can be the reason of some indetermination what method should be called in runtime, since there is two methods with the same selector). I use swizzling instead. And also I don't need to use tags.

Add this method to your view controller, where you have scroll view (self.categoriesTableView property is a table view where I want to show scroll bars)

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // Do swizzling to turn scroll indicator always on
    // Search correct subview with vertical scroll indicator image across tableView subviews
    for (UIView * view in self.categoriesTableView.subviews) {
        if ([view isKindOfClass:[UIImageView class]]) {
            if (view.alpha == 0 && view.autoresizingMask == UIViewAutoresizingFlexibleLeftMargin) {
                if (view.frame.size.width < 10 && view.frame.size.height > view.frame.size.width) {
                    if (self.categoriesTableView.frame.size.height < self.categoriesTableView.contentSize.height) {
                        // Swizzle class for found imageView, that should be scroll indicator
                        object_setClass(view, [AlwaysOpaqueImageView class]);
                        break;
                    }
                }
            }
        }
    }
    // Search correct subview with horizontal scroll indicator image across tableView subviews
    for (UIView * view in self.categoriesTableView.subviews) {
        if ([view isKindOfClass:[UIImageView class]]) {
            if (view.alpha == 0 && view.autoresizingMask == UIViewAutoresizingFlexibleTopMargin) {
                if (view.frame.size.height < 10 && view.frame.size.height < view.frame.size.width) {
                    if (self.categoriesTableView.frame.size.width < self.categoriesTableView.contentSize.width) {
                        // Swizzle class for found imageView, that should be scroll indicator
                        object_setClass(view, [AlwaysOpaqueImageView class]);
                        break;
                    }
                }
            }
        }
    }
    // Ask to flash indicator to turn it on
   [self.categoriesTableView flashScrollIndicators];
}

Add new class

@interface AlwaysOpaqueImageView : UIImageView
@end

@implementation AlwaysOpaqueImageView

- (void)setAlpha:(CGFloat)alpha {
    [super setAlpha:1.0];
}

@end

The scroll indicator (vertical scroll indicator in first for cycle and horizontal in second for cycle) will be always at the screen. If you need only one indicator, left only this for cycle in code and remove another one.

Solution 7 - Iphone

For webviews, where the first subview is a scrollview, in the latest SDK, if an HTML page is longer than the frame, no scroll bar is shown, and if the html content happens to line up with the frame, or you have a whitespace at the bottom of the frame, it 'looks' like there is no scroll needed and nothing below the line. In this case, I think you should definately flash the scroll bars in the delegate's

- (void)webViewDidFinishLoad:(UIWebView *)webView; 

method to alert the user that there is more stuff 'outside the box'.

NSArray *subViews = [[NSArray alloc] initWithArray:[webView subviews]] ;
UIScrollView *webScroller = (UIScrollView *)[subViews objectAtIndex:0] ;	

With HTML, the horizontal content is wrapped automatically, so check the webscroller height.

		if (webScroller.contentSize.height > webView.frame.size.height) {
			[webScroller flashScrollIndicators];
		}

The flash is so short, and happens while over views are loading, that it can be overlooked. To work around that, you could also jiggle or bounce or scroll or scale the content a little via the generic UIView commitAnimations

Solution 8 - Iphone

iOS does not offer the API. But if you really want this, you can add your custom indicator to scroll view and layout it yourself, just as the demo does:







(void)layoutSubviews
{
[super layoutSubviews];




if (self.showsVerticalScrollIndicatorAlways) {
scroll_indicator_position(self, k_scroll_indicator_vertical);
}




if (self.showsHorizontalScrollIndicatorAlways) {
scroll_indicator_position(self, k_scroll_indicator_horizontal);
}
}

The link is https://github.com/flexih/MazeScrollView

Solution 9 - Iphone

ScrollBar that functions just like the iOS built in one, but you can mess with the color and width.

-(void)persistantScrollBar
{
    [persistantScrollBar removeFromSuperview];
    [self.collectionView setNeedsLayout];
    [self.collectionView layoutIfNeeded];
    
    if (self.collectionView.contentSize.height > self.collectionView.frame.size.height + 10)
    {
        persistantScrollBar = [[UIView alloc] initWithFrame:(CGRectMake(self.view.frame.size.width - 10, self.collectionView.frame.origin.y, 5, (self.collectionView.frame.size.height /self.collectionView.contentSize.height) * self.collectionView.frame.size.height))];
        persistantScrollBar.backgroundColor = [UIColor colorWithRed:207/255.f green:207/255.f blue:207/255.f alpha:0.5f];
        persistantScrollBar.layer.cornerRadius = persistantScrollBar.frame.size.width/2;
        persistantScrollBar.layer.zPosition = 0;
        [self.view addSubview:persistantScrollBar];
    }
}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGRect rect = persistantScrollBar.frame;
    rect.origin.y =  scrollView.frame.origin.y + (scrollView.contentOffset.y *(self.collectionView.frame.size.height/self.collectionView.contentSize.height));
    rect.size.height = (self.collectionView.frame.size.height /self.collectionView.contentSize.height) * self.collectionView.frame.size.height;
    if ( scrollView.contentOffset.y <= 0 )
    {
        rect.origin.y = scrollView.frame.origin.y;
        rect.size.height = rect.size.height + (scrollView.contentOffset.y);
    }
    else if (scrollView.contentOffset.y + scrollView.frame.size.height >= scrollView.contentSize.height)
    {
        rect.size.height = rect.size.height - ((scrollView.contentOffset.y + scrollView.frame.size.height) - scrollView.contentSize.height);
        rect.origin.y =  (self.collectionView.frame.origin.y + self.collectionView.frame.size.height - 5) - rect.size.height;
    }

    persistantScrollBar.frame = rect;
}

Solution 10 - Iphone

Swift 3

You can access the scrollbar using scrollView.subviews and modify the alpha as shown here. It works for me.

extension UIScrollView {
    override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        for x in self.subviews {
            x.alpha = 1.0
        }
    }
}

extension MyScrollViewDelegate : UIScrollViewDelegate {
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        for x in scrollView.subviews {
            x.alpha = 1.0
        }
    }
}

Solution 11 - Iphone

Looking through these answers, most of them are downright scary. Got this working in Swift 5 with the following. It still depends on the scroll view using subviews with class "_UIScrollViewScrollIndicator" - but at least there's no swizzling or app wide categories.

class IndicatorScrollView: UIScrollView {
    
    weak var indicatorTimer: Timer?
    
    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        setupIndicatorTimer()
    }
    
    override func removeFromSuperview() {
        super.removeFromSuperview()
        indicatorTimer?.invalidate()
    }
    
    deinit {
        indicatorTimer?.invalidate()
    }
    
    func setupIndicatorTimer() {
        indicatorTimer?.invalidate()
        indicatorTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(showIndicators), userInfo: nil, repeats: true)
    }
    
    @objc func showIndicators() {
        subviews.forEach {
            if String(describing: type(of: $0)).contains("ScrollIndicator") {
                $0.alpha = 1
            }
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTom GView Question on Stackoverflow
Solution 1 - IphoneMongus PongView Answer on Stackoverflow
Solution 2 - IphoneVolodymyr B.View Answer on Stackoverflow
Solution 3 - IphoneBen SView Answer on Stackoverflow
Solution 4 - IphonesciasxpView Answer on Stackoverflow
Solution 5 - IphoneAbhinayMeView Answer on Stackoverflow
Solution 6 - IphoneAccid BrightView Answer on Stackoverflow
Solution 7 - IphonePapaSmurfView Answer on Stackoverflow
Solution 8 - IphoneflexihView Answer on Stackoverflow
Solution 9 - IphoneHannahCarneyView Answer on Stackoverflow
Solution 10 - IphoneAlfredView Answer on Stackoverflow
Solution 11 - IphonehundrethView Answer on Stackoverflow