Josh O'Connor Josh O'Connor - 5 months ago 201
iOS Question

Custom TableView Scroll indicator (iOS)

I have an app which uses a tableview, and sometimes there can be as many as 100 items in this tableview. Thus, I have been assigned to customize this scroll indicator, so that it is easy for the user to scroll through their contents. To do this, I need to implement the following:

1) I need the scroll indicator to always show.

2) I need to change the scroll indicator from the iOS default gray indicator to an orange one, and add a label in the middle of it which extends inward. This label will have the date of the cell. As you scroll down in the scroll bar, the date changes to reflect where you are on the page. (See image for clarification).

3) When you click and hold this custom TableView's scroll indicator, it enables fast scrolling.

What is the best way to approach this? Should I use a library?

enter image description here

Answer

This isn't a perfect solution but this what I came up with and this should get you on the right track to perfect this solution to your needs. Basically there are a few basic steps you need to do:

1 Instantiate a tableView (UITableView) and scrollViewIndicator (UIView)

2 Calculate the height of the scrollIndicator based upon the contentSize of the tableView. Then add both the tableView and scrollIndicator to the container view, and the scroll indicator above the tableView with it's alpha property set to 0 (to fade in one we scroll)

3 Check the contentOffset of the tableView (subclass of UIScrollView) and move the scrollIndicator based upon this value

4 Fade the scrollIndicator out once the tableView has decelerated

You're specific custom scroll indicator are going to determined by your project and needs. This should get you on the right track but I think your biggest issue is going to be calculating the height of the scrollIndicator once "paging" is introduced. But I have faith in you! Good luck my friend.

#import "ViewController.h"

static CGFloat indicatorPadding =          5;
static CGFloat indicatorHeightMultiplyer = 0.05;
static CGFloat indicatorWidth =            3;
static CGFloat indicatorShowAnimation =    0.10;

@interface ViewController () <UITableViewDataSource, UITableViewDelegate> {
    CGFloat lastScrollOffset;
    BOOL    isFadingIndicator;
}

@property (strong, nonatomic) UITableView *tableView;
@property (strong, nonatomic) UIView      *scrollIndicator;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    isFadingIndicator = NO;

    // Set Up TableView
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];
    self.tableView.rowHeight = 100;
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    self.tableView.showsHorizontalScrollIndicator = NO;
    self.tableView.showsVerticalScrollIndicator = NO;
    [self.tableView layoutIfNeeded];

    // Calculate indicator size based on TableView contentSize
    CGFloat indicatorHeight = self.tableView.contentSize.height * indicatorHeightMultiplyer;

    // Set Up Scroll Indicator
    self.scrollIndicator = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetWidth(self.tableView.frame) - indicatorPadding, indicatorPadding, indicatorWidth, indicatorHeight)];
    self.scrollIndicator.backgroundColor = [UIColor orangeColor];
    self.scrollIndicator.layer.cornerRadius = self.scrollIndicator.frame.size.width / 2;

    // Add TableView
    [self.view addSubview:self.tableView];

    // Add Scroll Indicator
    [self.view addSubview:self.scrollIndicator];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];

}

#pragma mark - TABLE VIEW METHODS

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

    return cell;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return 10;
}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {

    if (scrollView == self.tableView) {

        [self showIndicator];

        if (lastScrollOffset >= scrollView.contentOffset.y) {
            [self moveScrollIndicatorDownward:YES withOffset:scrollView.contentOffset.y];
        }
        else {
            [self moveScrollIndicatorDownward:NO withOffset:scrollView.contentOffset.y]; // upward
        }

        lastScrollOffset = scrollView.contentOffset.y;

    }

}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self hideIndicator];
}

#pragma mark - SCROLL INDICATOR METHODS

-(void)showIndicator {

    if (self.scrollIndicator.alpha == 0 && isFadingIndicator == NO) {
        // fade in scroll indicator
        isFadingIndicator = YES;
        [UIView animateWithDuration:indicatorShowAnimation animations:^{
            self.scrollIndicator.alpha = 1;
        } completion:^(BOOL finished) {
            isFadingIndicator = NO;
        }];

    }

}

-(void)hideIndicator {

    if (self.scrollIndicator.alpha == 1 && isFadingIndicator == NO) {
        // fade out scroll indicator
        isFadingIndicator = YES;
        [UIView animateWithDuration:indicatorShowAnimation animations:^{
            self.scrollIndicator.alpha = 0;
        } completion:^(BOOL finished) {
            isFadingIndicator = NO;
        }];
    }
}

-(void)moveScrollIndicatorDownward:(BOOL)downwards withOffset:(CGFloat)offset {

    if ([self canMoveScrollIndicator:downwards]) {

        if (downwards) {

            self.scrollIndicator.center = CGPointMake(CGRectGetMidX(self.scrollIndicator.frame), (CGRectGetHeight(self.scrollIndicator.frame) / 2) + offset);
        }
        else {

            self.scrollIndicator.center = CGPointMake(CGRectGetMidX(self.scrollIndicator.frame), (CGRectGetHeight(self.scrollIndicator.frame) / 2) + offset);
        }

    }
    else {
        // maybe 'bounce' scroll indicator if isDecelerting is YES?
    }

}

-(BOOL)canMoveScrollIndicator:(BOOL)downwards {

    if (downwards) {
        if (self.scrollIndicator.frame.origin.y >= self.tableView.frame.size.height - indicatorPadding) {
            return NO;
        }
        else {
            return YES;
        }
    }
    else {
        // upwards
        if ((self.scrollIndicator.frame.origin.y + self.scrollIndicator.frame.size.height) <= self.tableView.frame.origin.y + indicatorPadding) {
            return NO;
        }
        else {
            return YES;
        }
    }

}

@end