ios Changing UIScrollView scrollbar color to different colors

Viewed 38010

How can we change color of UIScrollview's scroll indicator to something like blue, green etc.

I know we can change it to white, black. But other then these colors.

Many Thanks

17 Answers

Here's more safe Swift 3 method:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let verticalIndicator = scrollView.subviews.last as? UIImageView
    verticalIndicator?.backgroundColor = UIColor.green
}

Based on the answer of @Alex (https://stackoverflow.com/a/58415249/3876285), I'm posting just a little improvement to change the color of scroll indicators.

extension UIScrollView {

    var scrollIndicators: (horizontal: UIView?, vertical: UIView?) {

        guard self.subviews.count >= 2 else {
            return (horizontal: nil, vertical: nil)
        }

        func viewCanBeScrollIndicator(view: UIView) -> Bool {
            let viewClassName = NSStringFromClass(type(of: view))
            if viewClassName == "_UIScrollViewScrollIndicator" || viewClassName == "UIImageView" {
                return true
            }
            return false
        }

        let horizontalScrollViewIndicatorPosition = self.subviews.count - 2
        let verticalScrollViewIndicatorPosition = self.subviews.count - 1

        var horizontalScrollIndicator: UIView?
        var verticalScrollIndicator: UIView?

        let viewForHorizontalScrollViewIndicator = self.subviews[horizontalScrollViewIndicatorPosition]
        if viewCanBeScrollIndicator(view: viewForHorizontalScrollViewIndicator) {
            horizontalScrollIndicator = viewForHorizontalScrollViewIndicator.subviews[0]
        }

        let viewForVerticalScrollViewIndicator = self.subviews[verticalScrollViewIndicatorPosition]
        if viewCanBeScrollIndicator(view: viewForVerticalScrollViewIndicator) {
            verticalScrollIndicator = viewForVerticalScrollViewIndicator.subviews[0]
        }
        return (horizontal: horizontalScrollIndicator, vertical: verticalScrollIndicator)
    }

}

If you don't add .subviews[0], you will get the deeper view and when you try to change the color of the indicator, this will appear with a weird white effect. That's because there is another view in front of it:

enter image description here

By adding .subviews[0] to each indicator view, once you try to change the color by calling:

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
    DispatchQueue.main.async() {
        scrollView.scrollIndicators.vertical?.backgroundColor = UIColor.yourcolor
    }
}

You will access to the first view and change the color properly:

enter image description here

Kudos to @Alex who posted a great solution

in IOS 13

Try this one

func scrollViewDidScroll(_ scrollView: UIScrollView){


        if #available(iOS 13, *) {
            (scrollView.subviews[(scrollView.subviews.count - 1)].subviews[0]).backgroundColor = UIColor.themeColor(1.0) //verticalIndicator
            (scrollView.subviews[(scrollView.subviews.count - 2)].subviews[0]).backgroundColor = UIColor.themeColor(1.0) //horizontalIndicator
        } else {
            if let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as? UIImageView) {
                verticalIndicator.backgroundColor = UIColor.themeColor(1.0)
            }

            if let horizontalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 2)] as? UIImageView) {
                horizontalIndicator.backgroundColor = UIColor.themeColor(1.0)
            }
        }
    }

in IOS 13

Since iOS13 scroll indicators have class _UIScrollViewScrollIndicator, not UIImageView.

Many people used code like

let verticalIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as! UIImageView)

It's not good idea, because they promised that last subview will be UIImageView :). Now it's not and they can get crash.

You can try following code to get scrollView indicators:

extension UIScrollView {

    var scrollIndicators: (horizontal: UIView?, vertical: UIView?) {

        guard self.subviews.count >= 2 else {
            return (horizontal: nil, vertical: nil)
        }

        func viewCanBeScrollIndicator(view: UIView) -> Bool {
            let viewClassName = NSStringFromClass(type(of: view))
            if viewClassName == "_UIScrollViewScrollIndicator" || viewClassName == "UIImageView" {
                return true
            }
            return false
        }

        let horizontalScrollViewIndicatorPosition = self.subviews.count - 2
        let verticalScrollViewIndicatorPosition = self.subviews.count - 1

        var horizontalScrollIndicator: UIView?
        var verticalScrollIndicator: UIView?

        let viewForHorizontalScrollViewIndicator = self.subviews[horizontalScrollViewIndicatorPosition]
        if viewCanBeScrollIndicator(view: viewForHorizontalScrollViewIndicator) {
            horizontalScrollIndicator = viewForHorizontalScrollViewIndicator
        }

        let viewForVerticalScrollViewIndicator = self.subviews[verticalScrollViewIndicatorPosition]
        if viewCanBeScrollIndicator(view: viewForVerticalScrollViewIndicator) {
            verticalScrollIndicator = viewForVerticalScrollViewIndicator
        }
        return (horizontal: horizontalScrollIndicator, vertical: verticalScrollIndicator)
    }

}

If you need only one (h or v indicator) - it's better to cut this func and keep only one you need (to improve perfomance).

Also it would be good to call update func inside of DispatchQueue, to keep smoothness of scrolling.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
   DispatchQueue.main.async {
       scrollView.updateCustomScrollIndicatorView()
   }
}

Here is what I did in Swift 4, similar to previous answers. In my case I'm recoloring the image to be invisible, set correct corner radius and only execute this process once.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let color = UIColor.red
    guard
        let verticalIndicator = scrollView.subviews.last as? UIImageView,
        verticalIndicator.backgroundColor != color,
        verticalIndicator.image?.renderingMode != .alwaysTemplate
    else { return }
    verticalIndicator.layer.masksToBounds = true
    verticalIndicator.layer.cornerRadius = verticalIndicator.frame.width / 2
    verticalIndicator.backgroundColor = color
    verticalIndicator.image = verticalIndicator.image?.withRenderingMode(.alwaysTemplate)
    verticalIndicator.tintColor = .clear
}

please use below code on iOS Renderer

 private bool _layouted;
 public override void LayoutSubviews()
 {
       base.LayoutSubviews();
       if (!_layouted)
       {
            this.Layer.BorderColor = UIColor.Red.CGColor;
            var Verticalbar = (UIImageView)this.Subviews[this.Subviews.Length - 1];

            Verticalbar.BackgroundColor = Color.FromHex("#0099ff").ToUIColor();
            var Horizontlebar = (UIImageView)this.Subviews[this.Subviews.Length - 2];

            Horizontlebar.BackgroundColor = Color.FromHex("#0099ff").ToUIColor();
            _layouted = true;
       }
}

As for iOS 13 subviews changed so adding simple if, solved this issues.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 13.0) {
    UIView *verticalIndicator = [scrollView.subviews lastObject];
    verticalIndicator.backgroundColor = [UIColor redColor];
} else {
    UIImageView *verticalIndicator = [scrollView.subviews lastObject];
    verticalIndicator.backgroundColor = [UIColor redColor];
}
}
Related