UIScrollView’s scrollbars customization. [Updated]

Multiple times I’ve faced a task to customize UIScrollView’s scrollbars and it always was challenging for me. There are no methods provided by Apple and it is very complex to implement UIScrollView replacement from scratch due to a not trivial behavior of interaction handling and bouncing behavior of content. Recently I came up with an implementation which I am most likely not going to change in the future. The idea of the solution is based on the fact that scrollbars are just subviews of UIScrollView. So, with this information in mind I implemented the following methods in categories:

@interface UIScrollView(CustomScrollbarPrivate)

-(UIImageView *)scrollbarImageViewWithIndex:(int)indexOffset;

@end

@implementation UIScrollView(CustomScrollbarPrivate)

-(UIImageView *)scrollbarImageViewWithIndex:(int)indexOffset  
{
   int viewsCount = [[self subviews] count];
   UIImageView *result = [[self subviews] objectAtIndex:viewsCount - indexOffset - 1];

   return result;
}

@end

Just because scrollbars are always over the content we do a search thru subviews of scroll we select views starting from the last one with 0 or 1 indices offset for Horizontal and Vertical scrollbars accordingly. As you can see from the code all scrollbars are actually UIImageViews. The code to extract them was moved to private category to avoid further code duplication.

As part of customization I decided to provide not only image adjustment but size of a scrollbar also, so I implemented methods below:

@interface UIScrollView(CustomScrollbar)

-(void)setHorizontalScrollbarImage:(UIImage *)image;
-(void)setVerticalScrollbarImage:(UIImage *)image;

-(void)setHorizontalScrollbarHeight:(int)height;
-(void)setVerticalScrollbarWidth:(int)width;

@end

static const int UIScrollViewHorizontalBarIndexOffset = 0;
static const int UIScrollViewVerticalBarIndexOffset = 1;

@implementation UIScrollView(CustomScrollbar)

-(void)setHorizontalScrollbarImage:(UIImage *)image 
{    
   UIImageView *scrollBar = [self scrollbarImageViewWithIndex: UIScrollViewHorizontalBarIndexOffset];
   [scrollBar setImage:image];
}

-(void)setVerticalScrollbarImage:(UIImage *)image 
{
   UIImageView *scrollBar = [self scrollbarImageViewWithIndex: UIScrollViewVerticalBarIndexOffset];
   [scrollBar setImage:image];    
}

-(void)setHorizontalScrollbarHeight:(int)height 
{
   UIImageView *scrollBar = [self scrollbarImageViewWithIndex: UIScrollViewHorizontalBarIndexOffset];
   

   CGRect frame = [scrollBar frame];
   frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, height);
   [scrollBar setFrame:frame];
}

-(void)setVerticalScrollbarWidth:(int)width 
{
   UIImageView *scrollBar = [self scrollbarImageViewWithIndex: UIScrollViewVerticalBarIndexOffset];
   

   CGRect frame = [scrollBar frame];
   frame = CGRectMake(frame.origin.x, frame.origin.y, width, frame.size.height);
   [scrollBar setFrame:frame];    
}

@end

As you can see all the code above is based on the previously implemented private method and doesn’t do anything special but sets an image or changes the frame of extracted UIImageView. This is sufficient to configure any aspect of a UIScrollView’s scrollbar. So, next step is to test this category in an application.

   UIImage *verticalScrollbarBackground = [UIImage imageNamed:@"custom_scrollbar_v.png"];
   verticalScrollbarBackground = [verticalScrollbarBackground stretchableImageWithLeftCapWidth:0 topCapHeight:5];

   UIImage *horizontalScrollbarBackground = [UIImage imageNamed:@"custom_scrollbar_h.png"];
   horizontalScrollbarBackground = [horizontalScrollbarBackground stretchableImageWithLeftCapWidth:5 topCapHeight:0];

   [scrollView setVerticalScrollbarImage: verticalScrollbarBackground];
   [scrollView setVerticalScrollbarWidth:20];

   [scrollView setHorizontalScrollbarImage: horizontalScrollbarBackground];
   [scrollView setHorizontalScrollbarHeight:20];

So the resulting code is very compact and does not require any additional code manipulations, all logic of scrollbars is pretty well encapsulated in a category. It is important to notice that an image needs to be stretchable horizontally or vertically depending on a scrollbar to get a nice looking effect like one shown below:

Full example and just a categories for reuse attached below:

Update: I got a multiple notifications that this code does not work in iOS 6.x. I checked it, it works perfectly well. Here is a couple of things that you need to remember before use of the code:

  • When you move category into the project be sure that UIScrollView+CustomScrollbar.m included into build phase.
  • Be sure that customization of scroll bars called after all the content added in other case scroll bars is not initialized yet and there is nothing to customize.

I am thinking about move this code sample to github and making it more universal for general use by writing a code that detects that some of the scroll bars is disabled or is not initialized yet etc. Should be a good way to simplify use of this code by others. Support me by commenting this article, so I will continue my journey :)

Tags: ,

6 comments

  1. Doesn’t work for me, scrollbar is just not accessible via scroll view’s subviews. For example, for a scroll view with one element [[self subviews] count] is 1, so I can get this element via subviews, but cannot get access to scrollbar

  2. Great, thanks!

  3. Indeed works in iOS5.1 :)

  4. Thank you very much!
    A helpful article

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>