I'm trying to find a library or sample project for the sliding categories view that Etsy has in their iPhone app.
I'm talking about re-creating the "Handpicked, History, and Seasonal" categories swipe effect at the top of the app's main view.
If anybody knows of one on Github, or maybe a kickstart on how to do something like this, that would be awesome!
If you want just the categories and not the whole TableView, then you don't need a 3rd party control. A UIScrollView is all you need.
The idea is you create a scrollview with Paging enabled and set it not to clip the contents and center it in the screen.
Now because we need to be able to capture touches even beyond the left edge of the scrollView (when the user has scrolled already), we need a trick to capture the touches. This is done by using a UIView that will be in a full screen width and will pass a long the touches intercepted to our scrollView.
With that set, here is the code:
First the View that captures the touches (I named it ExtendedScrollViewCaptureView):
#import <UIKit/UIKit.h>
@interface ExtendedScrollViewCaptureView : UIView {
}
@property (nonatomic,strong)UIScrollView *scrollView;
@end
Here is the implementation file:
#import "ExtendedScrollViewCaptureView.h"
@implementation ExtendedScrollViewCaptureView
@synthesize scrollView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (UIView *) hitTest:(CGPoint) point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
return scrollView;
}
return nil;
}
@end
Now lets go to the main thingy. Create a UIScrollView iVar in your viewController header file:
@property(nonatomic,strong)UIScrollView *scrollView;
also add 2 integer variables that monitor the maximum titles available and keep track of the selected tab:
@interface MyViewController : UIViewController<UIScrollViewDelegate>
{
int selectedIndex;
int maxIndex;
}
and in your implementation file:
- (void)viewDidLoad
{
[super viewDidLoad];
ExtendedScrollViewCaptureView *extendedView = [[ExtendedScrollViewCaptureView alloc] initWithFrame:self.navigationBar.bounds];
extendedView.backgroundColor = [UIColor clearColor];
extendedView.clipsToBounds = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(labelTapped:)];
[extendedView addGestureRecognizer:tap];
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.frame = CGRectMake(0,0,320,36);
self.scrollView.pagingEnabled = YES;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.bounces = YES;
self.scrollView.alwaysBounceHorizontal = YES;
self.scrollView.alwaysBounceVertical = NO;
self.scrollView.backgroundColor = [UIColor clearColor];
self.scrollView.delegate = self;
self.scrollView.scrollsToTop = NO;
//add the scrollView inside the extendedView
[extendedView addSubview:self.scrollView];
//get the pointer reference
extendedView.scrollView = self.scrollView;
//add the arrow inside the extendedView
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"arrow.png"]];
arrow.frame = CGRectMake(154, 36, 11, 6);
[extendedView addSubview:arrow];
//add the extendedSubView to the view
[self.view addSubview:extendedView];
//init the scrollView with some entries:
[self setUpScrollView:[NSArray arrayWithObjects:@"LABEL 1",@"LABEL 2",@"LABEL 3",@"LABEL 4",@"LABEL 5",nil]];
}
Now create the function to init your ScrollView with the title labels (the labels being passed as an NSArray)
- (void)setUpScrollView:(NSArray *)titleLabels {
int scrollSize = 320;
int i = 0;
int offsetX = 0;
int scrollViewWidth = 0;
maxIndex = titleLabels.count;
//if our scrollview has already the labels stop here
if ([self.scrollView subviews].count>0) {
self.scrollView.contentOffset = CGPointZero;
return;
}
//get the max width of the labels, which will define our label width
for (NSString *titleLabel in titleLabels) {
CGSize expectedLabelSize = [[titleLabel capitalizedString] sizeWithFont:[UIFont fontWithName:kFontFamily1 size:kFontFamily1Correction+13] constrainedToSize:CGSizeMake(320, 22)];
scrollViewWidth = MAX(scrollViewWidth,expectedLabelSize.width);
}
//restrict max width for title items to 106 pixels (to fit 3 labels in the screen)
//this is optional and can adjusted or removed, but I suggest to make labels equal width
scrollViewWidth = MIN(scrollViewWidth, 106);
//now draw the labels
for (NSString *titleLabel in titleLabels) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(offsetX, 5, scrollViewWidth, 34)];
label.text = [titleLabel capitalizedString];
label.adjustsFontSizeToFitWidth = NO;
label.numberOfLines = 2;
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont fontWithName:@"ArialMT" size:13];
if (i==selectedItem) {
label.textColor = [UIColor redColor];
}
else {
label.textColor = [UIColor whiteColor];
}
label.textAlignment = UITextAlignmentCenter;
label.tag = 23000+i;
[self.scrollView addSubview:label];
offsetX+=scrollViewWidth;
i++;
}
self.scrollView.frame = CGRectMake((320-scrollViewWidth)/2, 0, scrollViewWidth, 36);
self.scrollView.clipsToBounds = NO;
self.scrollView.contentSize = CGSizeMake(MAX(scrollSize,offsetX), 36);
self.scrollView.contentOffset = CGPointMake(scrollViewWidth*selectedItem, 0);
}
Now capture the UIScrollView scroll event, from the delegate:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
//get the index of the label we scrolled into!
int visiblePageIndex = round(scrollView.contentOffset.x/scrollView.bounds.size.width);
//set page number..
if (selectedIndex!=visiblePageIndex) {
//get the label and set it to red
UILabel *label = (UILabel*)[self.scrollView viewWithTag:23000+visiblePageIndex];
label.textColor = [UIColor redColor];
//get the previous Label and set it back to White
UILabel *oldLabel = (UILabel*)[self.scrollView viewWithTag:23000+selectedIndex];
oldLabel.textColor = [UIColor whiteColor];
//set the new index to the index holder
selectedIndex = visiblePageIndex;
}
}
finally we need the function to capture the title tap events:
- (void)labelTapped:(UITapGestureRecognizer*)gestureRecognizer {
CGPoint pressPoint = [gestureRecognizer locationInView:gestureRecognizer.view];
if (pressPoint.x>(self.scrollView.frame.size.width+self.view.frame.size.width)/2) {
//move to next page if one is available...
if (selectedIndex+1<maxIndex) {
float currentOffset = self.scrollView.contentOffset.x+self.scrollView.frame.size.width;
[self.scrollView setContentOffset:CGPointMake(currentOffset, 0) animated:YES];
}
}
else if (pressPoint.x<(self.view.frame.size.width-self.scrollView.frame.size.width)/2) {
//move to previous page if one is available
if (selectedIndex>0) {
float currentOffset = self.scrollView.contentOffset.x-self.scrollView.frame.size.width;
[self.scrollView setContentOffset:CGPointMake(currentOffset, 0) animated:YES];
}
}
}
That's it!
There's a similar control up on github at https://github.com/rs/SDSegmentedControl. They use a UISegmentedControl subclass to create the effect. Cocoacontrols.com is your friend. :)