Repository: daria-kopaliani/DAPagesContainer Branch: master Commit: e203cc38b97f Files: 20 Total size: 60.9 KB Directory structure: gitextract_3pgm1uog/ ├── .gitignore ├── DAPagesContainer/ │ ├── DAPageIndicatorView.h │ ├── DAPageIndicatorView.m │ ├── DAPagesContainer.h │ ├── DAPagesContainer.m │ ├── DAPagesContainerTopBar.h │ └── DAPagesContainerTopBar.m ├── DAPagesContainer.podspec ├── DAPagesContainerDemo/ │ ├── DAAppDelegate.h │ ├── DAAppDelegate.m │ ├── DAPagesContainerDemo-Info.plist │ ├── DAPagesContainerDemo-Prefix.pch │ ├── DAViewController.h │ ├── DAViewController.m │ ├── en.lproj/ │ │ ├── InfoPlist.strings │ │ └── MainStoryboard.storyboard │ └── main.m ├── DAPagesContainerDemo.xcodeproj/ │ └── project.pbxproj ├── LICENSE └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode .DS_Store build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 *.xcworkspace !default.xcworkspace xcuserdata profile *.moved-aside DerivedData .idea/ ================================================ FILE: DAPagesContainer/DAPageIndicatorView.h ================================================ // // DAPageIndicatorView.h // DAPagesContainerScrollView // // Created by Daria Kopaliani on 5/29/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import @interface DAPageIndicatorView : UIView @property (strong, nonatomic) UIColor *color; @end ================================================ FILE: DAPagesContainer/DAPageIndicatorView.m ================================================ // // DAPageIndicatorView.m // DAPagesContainerScrollView // // Created by Daria Kopaliani on 5/29/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import "DAPageIndicatorView.h" @implementation DAPageIndicatorView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.opaque = NO; _color = [UIColor blackColor]; } return self; } #pragma mark - Public - (void)setColor:(UIColor *)color { if (![_color isEqual:color]) { _color = color; [self setNeedsDisplay]; } } #pragma mark - Private - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextClearRect(context, rect); CGContextBeginPath(context); CGContextMoveToPoint (context, CGRectGetMinX(rect), CGRectGetMinY(rect)); CGContextAddLineToPoint(context, CGRectGetMidX(rect), CGRectGetMaxY(rect)); CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMinY(rect)); CGContextClosePath(context); CGContextSetFillColorWithColor(context, self.color.CGColor); CGContextFillPath(context); } @end ================================================ FILE: DAPagesContainer/DAPagesContainer.h ================================================ // // DAPageContainerScrollView.h // DAPagesContainerScrollView // // Created by Daria Kopaliani on 5/29/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import @interface DAPagesContainer : UIViewController /** The view controllers to be displayed in DAPagesContainer in order they appear in this array. @discussion view objects for all the view controllers will be resized to fit the container bounds together with topBar. Normally you should have more than one view controller. @warning all view controllers should have nonempty titles which are displayed in the top bar. */ @property (strong, nonatomic) NSArray *viewControllers; /** An index of the selected view controller. */ @property (assign, nonatomic) NSUInteger selectedIndex; /** A hight of the top bar. Every time this value is changed, view objects for all the view controllers are resized. This is 44. by default. */ @property (assign, nonatomic) NSUInteger topBarHeight; /** An optional image page for the page indicator view This is {22., 9.} by default. */ @property (strong, nonatomic) UIImage *pageIndicatorImage; /** A size of the page indicator view. @discussion if this property is not nil 'pageIndicatorViewSize' property value will be ignored, size for the page indicator image view will equal the size of 'pageIndicatorImage' */ @property (assign, nonatomic) CGSize pageIndicatorViewSize; /** An optional background image of the top bar. */ @property (strong, nonatomic) UIImage *topBarBackgroundImage; /** A background color of the top bar. This is black by default. */ @property (strong, nonatomic) UIColor *topBarBackgroundColor; /** A font for all the buttons displayed in the top bar. This is system font of sixe 12. by default. */ @property (strong, nonatomic) UIFont *topBarItemLabelsFont; /** A color of all the view titles displayed on the top bar except for the selected one. This is light gray by default. */ @property (strong, nonatomic) UIColor *pageItemsTitleColor; /** A color of the selected view titles displayed on the top bar. This is white by default. */ @property (strong, nonatomic) UIColor *selectedPageItemTitleColor; /** Changes 'selectedIndex' property value and navigates to the newly selected view controller @param selectedIndex This mathod throws exeption if selectedIndex is out of range of the 'viewControllers' array @param animated Defines whether to present the corresponding view controller animated @discussion If 'animated' is YES and the newly selected view is not "the closest neighbor" of the previous selected view, all the intermediate views will be skipped for the sake of nice animation */ - (void)setSelectedIndex:(NSUInteger)selectedIndex animated:(BOOL)animated; /** Makes sure that view objects for all the view controllers are properly resized to fit the container bounds after device orientation was changed */ - (void)updateLayoutForNewOrientation:(UIInterfaceOrientation)orientation; @end ================================================ FILE: DAPagesContainer/DAPagesContainer.m ================================================ // // DAPageContainerScrollView.m // DAPagesContainerScrollView // // Created by Daria Kopaliani on 5/29/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import "DAPagesContainer.h" #import "DAPagesContainerTopBar.h" #import "DAPageIndicatorView.h" @interface DAPagesContainer () @property (strong, nonatomic) DAPagesContainerTopBar *topBar; @property (strong, nonatomic) UIScrollView *scrollView; @property (weak, nonatomic) UIScrollView *observingScrollView; @property (strong, nonatomic) UIView *pageIndicatorView; @property ( assign, nonatomic) BOOL shouldObserveContentOffset; @property (readonly, assign, nonatomic) CGFloat scrollWidth; @property (readonly, assign, nonatomic) CGFloat scrollHeight; - (void)layoutSubviews; - (void)startObservingContentOffsetForScrollView:(UIScrollView *)scrollView; - (void)stopObservingContentOffset; @end @implementation DAPagesContainer #pragma mark - Initialization - (id)init { self = [super init]; if (self) { [self setUp]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self setUp]; } return self; } - (void)dealloc { [self stopObservingContentOffset]; } - (void)setUp { _topBarHeight = 44.; _topBarBackgroundColor = [UIColor colorWithWhite:0.1 alpha:1.]; _topBarItemLabelsFont = [UIFont systemFontOfSize:12]; _pageIndicatorViewSize = CGSizeMake(22., 9.); self.pageItemsTitleColor = [UIColor lightGrayColor]; self.selectedPageItemTitleColor = [UIColor whiteColor]; } #pragma mark - View life cycle - (void)viewDidLoad { [super viewDidLoad]; self.shouldObserveContentOffset = YES; self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0., self.topBarHeight, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) - self.topBarHeight)]; self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.scrollView.delegate = self; self.scrollView.pagingEnabled = YES; self.scrollView.showsHorizontalScrollIndicator = NO; [self.view addSubview:self.scrollView]; [self startObservingContentOffsetForScrollView:self.scrollView]; self.topBar = [[DAPagesContainerTopBar alloc] initWithFrame:CGRectMake(0., 0., CGRectGetWidth(self.view.frame), self.topBarHeight)]; self.topBar.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth; self.topBar.itemTitleColor = self.pageItemsTitleColor; self.topBar.delegate = self; [self.view addSubview:self.topBar]; self.topBar.backgroundColor = self.topBarBackgroundColor; } - (void)viewDidUnload { [self stopObservingContentOffset]; self.scrollView = nil; self.topBar = nil; self.pageIndicatorView = nil; [super viewDidUnload]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self layoutSubviews]; } #pragma mark - Public - (void)setSelectedIndex:(NSUInteger)selectedIndex animated:(BOOL)animated { NSAssert(selectedIndex < self.viewControllers.count, @"selectedIndex should belong within the range of the view controllers array"); UIButton *previosSelectdItem = self.topBar.itemViews[self.selectedIndex]; UIButton *nextSelectdItem = self.topBar.itemViews[selectedIndex]; if (abs(self.selectedIndex - selectedIndex) <= 1) { [self.scrollView setContentOffset:CGPointMake(selectedIndex * self.scrollWidth, 0.) animated:animated]; if (selectedIndex == _selectedIndex) { self.pageIndicatorView.center = CGPointMake([self.topBar centerForSelectedItemAtIndex:selectedIndex].x, [self pageIndicatorCenterY]); } [UIView animateWithDuration:(animated) ? 0.3 : 0. delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:^ { [previosSelectdItem setTitleColor:self.pageItemsTitleColor forState:UIControlStateNormal]; [nextSelectdItem setTitleColor:self.selectedPageItemTitleColor forState:UIControlStateNormal]; } completion:nil]; } else { // This means we should "jump" over at least one view controller self.shouldObserveContentOffset = NO; BOOL scrollingRight = (selectedIndex > self.selectedIndex); UIViewController *leftViewController = self.viewControllers[MIN(self.selectedIndex, selectedIndex)]; UIViewController *rightViewController = self.viewControllers[MAX(self.selectedIndex, selectedIndex)]; leftViewController.view.frame = CGRectMake(0., 0., self.scrollWidth, self.scrollHeight); rightViewController.view.frame = CGRectMake(self.scrollWidth, 0., self.scrollWidth, self.scrollHeight); self.scrollView.contentSize = CGSizeMake(2 * self.scrollWidth, self.scrollHeight); CGPoint targetOffset; if (scrollingRight) { self.scrollView.contentOffset = CGPointZero; targetOffset = CGPointMake(self.scrollWidth, 0.); } else { self.scrollView.contentOffset = CGPointMake(self.scrollWidth, 0.); targetOffset = CGPointZero; } [self.scrollView setContentOffset:targetOffset animated:YES]; [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ self.pageIndicatorView.center = CGPointMake([self.topBar centerForSelectedItemAtIndex:selectedIndex].x, [self pageIndicatorCenterY]); self.topBar.scrollView.contentOffset = [self.topBar contentOffsetForSelectedItemAtIndex:selectedIndex]; [previosSelectdItem setTitleColor:self.pageItemsTitleColor forState:UIControlStateNormal]; [nextSelectdItem setTitleColor:self.selectedPageItemTitleColor forState:UIControlStateNormal]; } completion:^(BOOL finished) { for (NSUInteger i = 0; i < self.viewControllers.count; i++) { UIViewController *viewController = self.viewControllers[i]; viewController.view.frame = CGRectMake(i * self.scrollWidth, 0., self.scrollWidth, self.scrollHeight); [self.scrollView addSubview:viewController.view]; } self.scrollView.contentSize = CGSizeMake(self.scrollWidth * self.viewControllers.count, self.scrollHeight); [self.scrollView setContentOffset:CGPointMake(selectedIndex * self.scrollWidth, 0.) animated:NO]; self.scrollView.userInteractionEnabled = YES; self.shouldObserveContentOffset = YES; }]; } _selectedIndex = selectedIndex; } - (void)updateLayoutForNewOrientation:(UIInterfaceOrientation)orientation { [self layoutSubviews]; } #pragma mark * Overwritten setters - (void)setPageIndicatorViewSize:(CGSize)size { if ([self.pageIndicatorView isKindOfClass:[DAPageIndicatorView class]]) { if (!CGSizeEqualToSize(self.pageIndicatorView.frame.size, size)) { _pageIndicatorViewSize = size; [self layoutSubviews]; } } } - (void)setPageItemsTitleColor:(UIColor *)pageItemsTitleColor { if (![_pageItemsTitleColor isEqual:pageItemsTitleColor]) { _pageItemsTitleColor = pageItemsTitleColor; self.topBar.itemTitleColor = pageItemsTitleColor; [self.topBar.itemViews[self.selectedIndex] setTitleColor:self.selectedPageItemTitleColor forState:UIControlStateNormal]; } } - (void)setSelectedIndex:(NSUInteger)selectedIndex { [self setSelectedIndex:selectedIndex animated:NO]; } - (void)setSelectedPageItemTitleColor:(UIColor *)selectedPageItemTitleColor { if (![_selectedPageItemTitleColor isEqual:selectedPageItemTitleColor]) { _selectedPageItemTitleColor = selectedPageItemTitleColor; [self.topBar.itemViews[self.selectedIndex] setTitleColor:selectedPageItemTitleColor forState:UIControlStateNormal]; } } - (void)setTopBarBackgroundColor:(UIColor *)topBarBackgroundColor { _topBarBackgroundColor = topBarBackgroundColor; self.topBar.backgroundColor = topBarBackgroundColor; if ([self.pageIndicatorView isKindOfClass:[DAPageIndicatorView class]]) { [(DAPageIndicatorView *)self.pageIndicatorView setColor:topBarBackgroundColor]; } } - (void)setTopBarBackgroundImage:(UIImage *)topBarBackgroundImage { self.topBar.backgroundImage = topBarBackgroundImage; } - (void)setTopBarHeight:(NSUInteger)topBarHeight { if (_topBarHeight != topBarHeight) { _topBarHeight = topBarHeight; [self layoutSubviews]; } } - (void)setTopBarItemLabelsFont:(UIFont *)font { self.topBar.font = font; } - (void)setViewControllers:(NSArray *)viewControllers { if (_viewControllers != viewControllers) { _viewControllers = viewControllers; self.topBar.itemTitles = [viewControllers valueForKey:@"title"]; for (UIViewController *viewController in viewControllers) { [viewController willMoveToParentViewController:self]; viewController.view.frame = CGRectMake(0., 0., CGRectGetWidth(self.scrollView.frame), self.scrollHeight); [self.scrollView addSubview:viewController.view]; [viewController didMoveToParentViewController:self]; } [self layoutSubviews]; self.selectedIndex = 0; self.pageIndicatorView.center = CGPointMake([self.topBar centerForSelectedItemAtIndex:self.selectedIndex].x, [self pageIndicatorCenterY]); } } - (void)setPageIndicatorImage:(UIImage *)pageIndicatorImage { _pageIndicatorImage = pageIndicatorImage; self.pageIndicatorViewSize = (pageIndicatorImage) ? pageIndicatorImage.size : self.pageIndicatorViewSize; if ((pageIndicatorImage && [self.pageIndicatorView isKindOfClass:[DAPageIndicatorView class]]) || (!pageIndicatorImage && [self.pageIndicatorView isKindOfClass:[UIImageView class]])) { [self.pageIndicatorView removeFromSuperview]; self.pageIndicatorView = nil; } if (pageIndicatorImage) { if ([self.pageIndicatorView isKindOfClass:[DAPageIndicatorView class]]) { [self.pageIndicatorView removeFromSuperview]; self.pageIndicatorView = nil; } [(UIImageView *)self.pageIndicatorView setImage:pageIndicatorImage]; } else { if ([self.pageIndicatorView isKindOfClass:[UIImageView class]]) { [self.pageIndicatorView removeFromSuperview]; self.pageIndicatorView = nil; } [(DAPageIndicatorView *)self.pageIndicatorView setColor:self.topBarBackgroundColor]; } } #pragma mark - Private - (void)layoutSubviews { self.topBar.frame = CGRectMake(0., 0., CGRectGetWidth(self.view.bounds), self.topBarHeight); CGFloat x = 0.; for (UIViewController *viewController in self.viewControllers) { viewController.view.frame = CGRectMake(x, 0, CGRectGetWidth(self.scrollView.frame), self.scrollHeight); x += CGRectGetWidth(self.scrollView.frame); } self.scrollView.contentSize = CGSizeMake(x, self.scrollHeight); [self.scrollView setContentOffset:CGPointMake(self.selectedIndex * self.scrollWidth, 0.) animated:YES]; self.pageIndicatorView.center = CGPointMake([self.topBar centerForSelectedItemAtIndex:self.selectedIndex].x, [self pageIndicatorCenterY]); self.topBar.scrollView.contentOffset = [self.topBar contentOffsetForSelectedItemAtIndex:self.selectedIndex]; self.scrollView.userInteractionEnabled = YES; } - (CGFloat)pageIndicatorCenterY { return CGRectGetMaxY(self.topBar.frame) - 2. + CGRectGetHeight(self.pageIndicatorView.frame) / 2.; } - (UIView *)pageIndicatorView { if (!_pageIndicatorView) { if (self.pageIndicatorImage) { _pageIndicatorView = [[UIImageView alloc] initWithImage:self.pageIndicatorImage]; } else { _pageIndicatorView = [[DAPageIndicatorView alloc] initWithFrame:CGRectMake(0., 44, self.pageIndicatorViewSize.width, self.pageIndicatorViewSize.height)]; [(DAPageIndicatorView *)_pageIndicatorView setColor:self.topBarBackgroundColor]; } [self.view addSubview:self.pageIndicatorView]; } return _pageIndicatorView; } - (CGFloat)scrollHeight { return CGRectGetHeight(self.view.frame) - self.topBarHeight; } - (CGFloat)scrollWidth { return CGRectGetWidth(self.scrollView.frame); } - (void)startObservingContentOffsetForScrollView:(UIScrollView *)scrollView { [scrollView addObserver:self forKeyPath:@"contentOffset" options:0 context:nil]; self.observingScrollView = scrollView; } - (void)stopObservingContentOffset { if (self.observingScrollView) { [self.observingScrollView removeObserver:self forKeyPath:@"contentOffset"]; self.observingScrollView = nil; } } #pragma mark - DAPagesContainerTopBar delegate - (void)itemAtIndex:(NSUInteger)index didSelectInPagesContainerTopBar:(DAPagesContainerTopBar *)bar { [self setSelectedIndex:index animated:YES]; } #pragma mark - UIScrollView delegate - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { self.selectedIndex = scrollView.contentOffset.x / CGRectGetWidth(self.scrollView.frame); self.scrollView.userInteractionEnabled = YES; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { self.scrollView.userInteractionEnabled = YES; } } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { self.scrollView.userInteractionEnabled = YES; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { self.scrollView.userInteractionEnabled = NO; } #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { CGFloat oldX = self.selectedIndex * CGRectGetWidth(self.scrollView.frame); if (oldX != self.scrollView.contentOffset.x && self.shouldObserveContentOffset) { BOOL scrollingTowards = (self.scrollView.contentOffset.x > oldX); NSInteger targetIndex = (scrollingTowards) ? self.selectedIndex + 1 : self.selectedIndex - 1; if (targetIndex >= 0 && targetIndex < self.viewControllers.count) { CGFloat ratio = (self.scrollView.contentOffset.x - oldX) / CGRectGetWidth(self.scrollView.frame); CGFloat previousItemContentOffsetX = [self.topBar contentOffsetForSelectedItemAtIndex:self.selectedIndex].x; CGFloat nextItemContentOffsetX = [self.topBar contentOffsetForSelectedItemAtIndex:targetIndex].x; CGFloat previousItemPageIndicatorX = [self.topBar centerForSelectedItemAtIndex:self.selectedIndex].x; CGFloat nextItemPageIndicatorX = [self.topBar centerForSelectedItemAtIndex:targetIndex].x; UIButton *previosSelectedItem = self.topBar.itemViews[self.selectedIndex]; UIButton *nextSelectedItem = self.topBar.itemViews[targetIndex]; CGFloat red, green, blue, alpha, highlightedRed, highlightedGreen, highlightedBlue, highlightedAlpha; [self getRed:&red green:&green blue:&blue alpha:&alpha fromColor:self.pageItemsTitleColor]; [self getRed:&highlightedRed green:&highlightedGreen blue:&highlightedBlue alpha:&highlightedAlpha fromColor:self.selectedPageItemTitleColor]; CGFloat absRatio = fabsf(ratio); UIColor *prev = [UIColor colorWithRed:red * absRatio + highlightedRed * (1 - absRatio) green:green * absRatio + highlightedGreen * (1 - absRatio) blue:blue * absRatio + highlightedBlue * (1 - absRatio) alpha:alpha * absRatio + highlightedAlpha * (1 - absRatio)]; UIColor *next = [UIColor colorWithRed:red * (1 - absRatio) + highlightedRed * absRatio green:green * (1 - absRatio) + highlightedGreen * absRatio blue:blue * (1 - absRatio) + highlightedBlue * absRatio alpha:alpha * (1 - absRatio) + highlightedAlpha * absRatio]; [previosSelectedItem setTitleColor:prev forState:UIControlStateNormal]; [nextSelectedItem setTitleColor:next forState:UIControlStateNormal]; if (scrollingTowards) { self.topBar.scrollView.contentOffset = CGPointMake(previousItemContentOffsetX + (nextItemContentOffsetX - previousItemContentOffsetX) * ratio , 0.); self.pageIndicatorView.center = CGPointMake(previousItemPageIndicatorX + (nextItemPageIndicatorX - previousItemPageIndicatorX) * ratio, [self pageIndicatorCenterY]); } else { self.topBar.scrollView.contentOffset = CGPointMake(previousItemContentOffsetX - (nextItemContentOffsetX - previousItemContentOffsetX) * ratio , 0.); self.pageIndicatorView.center = CGPointMake(previousItemPageIndicatorX - (nextItemPageIndicatorX - previousItemPageIndicatorX) * ratio, [self pageIndicatorCenterY]); } } } } - (void)getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha fromColor:(UIColor *)color { const CGFloat *components = CGColorGetComponents(color.CGColor); CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)); if (colorSpaceModel == kCGColorSpaceModelRGB && CGColorGetNumberOfComponents(color.CGColor) == 4) { *red = components[0]; *green = components[1]; *blue = components[2]; *alpha = components[3]; } else if (colorSpaceModel == kCGColorSpaceModelMonochrome && CGColorGetNumberOfComponents(color.CGColor) == 2) { *red = *green = *blue = components[0]; *alpha = components[1]; } else { *red = *green = *blue = *alpha = 0; } } @end ================================================ FILE: DAPagesContainer/DAPagesContainerTopBar.h ================================================ // // DAPagesContainerTopBar.h // DAPagesContainerScrollView // // Created by Daria Kopaliani on 5/29/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import @class DAPagesContainerTopBar; @protocol DAPagesContainerTopBarDelegate - (void)itemAtIndex:(NSUInteger)index didSelectInPagesContainerTopBar:(DAPagesContainerTopBar *)bar; @end @interface DAPagesContainerTopBar : UIView @property (strong, nonatomic) UIImage *backgroundImage; @property (strong, nonatomic) UIColor *itemTitleColor; @property (strong, nonatomic) NSArray *itemTitles; @property (strong, nonatomic) UIFont *font; @property (readonly, strong, nonatomic) NSArray *itemViews; @property (readonly, strong, nonatomic) UIScrollView *scrollView; @property (weak, nonatomic) id delegate; - (CGPoint)centerForSelectedItemAtIndex:(NSUInteger)index; - (CGPoint)contentOffsetForSelectedItemAtIndex:(NSUInteger)index; @end ================================================ FILE: DAPagesContainer/DAPagesContainerTopBar.m ================================================ // // DAPagesContainerTopBar.m // DAPagesContainerScrollView // // Created by Daria Kopaliani on 5/29/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import "DAPagesContainerTopBar.h" @interface DAPagesContainerTopBar () @property (strong, nonatomic) UIImageView *backgroundImageView; @property (strong, nonatomic) UIScrollView *scrollView; @property (strong, nonatomic) NSArray *itemViews; - (void)layoutItemViews; @end @implementation DAPagesContainerTopBar CGFloat const DAPagesContainerTopBarItemViewWidth = 100.; CGFloat const DAPagesContainerTopBarItemsOffset = 30.; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.scrollView.showsHorizontalScrollIndicator = NO; [self addSubview:self.scrollView]; self.font = [UIFont systemFontOfSize:14]; self.itemTitleColor = [UIColor whiteColor]; } return self; } #pragma mark - Public - (CGPoint)centerForSelectedItemAtIndex:(NSUInteger)index { CGPoint center = ((UIView *)self.itemViews[index]).center; CGPoint offset = [self contentOffsetForSelectedItemAtIndex:index]; center.x -= offset.x - (CGRectGetMinX(self.scrollView.frame)); return center; } - (CGPoint)contentOffsetForSelectedItemAtIndex:(NSUInteger)index { if (self.itemViews.count < index || self.itemViews.count == 1) { return CGPointZero; } else { CGFloat totalOffset = self.scrollView.contentSize.width - CGRectGetWidth(self.scrollView.frame); return CGPointMake(index * totalOffset / (self.itemViews.count - 1), 0.); } } - (void)setItemTitleColor:(UIColor *)itemTitleColor { if (![_itemTitleColor isEqual:itemTitleColor]) { _itemTitleColor = itemTitleColor; for (UIButton *button in self.itemViews) { [button setTitleColor:itemTitleColor forState:UIControlStateNormal]; } } } #pragma mark * Overwritten setters - (void)setBackgroundImage:(UIImage *)backgroundImage { self.backgroundImageView.image = backgroundImage; } - (void)setItemTitles:(NSArray *)itemTitles { if (_itemTitles != itemTitles) { _itemTitles = itemTitles; NSMutableArray *mutableItemViews = [NSMutableArray arrayWithCapacity:itemTitles.count]; for (NSUInteger i = 0; i < itemTitles.count; i++) { UIButton *itemView = [self addItemView]; [itemView setTitle:itemTitles[i] forState:UIControlStateNormal]; [mutableItemViews addObject:itemView]; } self.itemViews = [NSArray arrayWithArray:mutableItemViews]; [self layoutItemViews]; } } - (void)setFont:(UIFont *)font { if (![_font isEqual:font]) { _font = font; for (UIButton *itemView in self.itemViews) { [itemView.titleLabel setFont:font]; } } } #pragma mark - Private - (UIButton *)addItemView { CGRect frame = CGRectMake(0., 0., DAPagesContainerTopBarItemViewWidth, CGRectGetHeight(self.frame)); UIButton *itemView = [[UIButton alloc] initWithFrame:frame]; [itemView addTarget:self action:@selector(itemViewTapped:) forControlEvents:UIControlEventTouchUpInside]; itemView.titleLabel.font = self.font; [itemView setTitleColor:self.itemTitleColor forState:UIControlStateNormal]; [self.scrollView addSubview:itemView]; return itemView; } - (void)itemViewTapped:(UIButton *)sender { [self.delegate itemAtIndex:[self.itemViews indexOfObject:sender] didSelectInPagesContainerTopBar:self]; } - (void)layoutItemViews { CGFloat x = DAPagesContainerTopBarItemsOffset; for (NSUInteger i = 0; i < self.itemViews.count; i++) { CGFloat width = [self.itemTitles[i] sizeWithFont:self.font].width; UIView *itemView = self.itemViews[i]; itemView.frame = CGRectMake(x, 0., width, CGRectGetHeight(self.frame)); x += width + DAPagesContainerTopBarItemsOffset; } self.scrollView.contentSize = CGSizeMake(x, CGRectGetHeight(self.scrollView.frame)); CGRect frame = self.scrollView.frame; if (CGRectGetWidth(self.frame) > x) { frame.origin.x = (CGRectGetWidth(self.frame) - x) / 2.; frame.size.width = x; } else { frame.origin.x = 0.; frame.size.width = CGRectGetWidth(self.frame); } self.scrollView.frame = frame; } - (void)layoutSubviews { [super layoutSubviews]; [self layoutItemViews]; } #pragma mark * Lazy getters - (UIImageView *)backgroundImageView { if (!_backgroundImageView) { _backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds]; _backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self insertSubview:_backgroundImageView belowSubview:self.scrollView]; } return _backgroundImageView; } @end ================================================ FILE: DAPagesContainer.podspec ================================================ Pod::Spec.new do |s| s.name = "DAPagesContainer" s.version = "1.0.1" s.summary = "A generic views container with a scrollable top bar." s.homepage = "https://github.com/daria-kopaliani/DAPagesContainer.git" s.license = 'MIT' s.author = { "Daria Kopaliani" => "daria.kopaliani@gmail.com" } s.source = { :git => "https://github.com/daria-kopaliani/DAPagesContainer.git", :tag => "1.0.1" } s.platform = :ios, '5.0' s.source_files = 'DAPagesContainer/**/*.{h,m}' s.requires_arc = true end ================================================ FILE: DAPagesContainerDemo/DAAppDelegate.h ================================================ // // DAAppDelegate.h // DAPagesContainerDemo // // Created by Daria Kopaliani on 5/30/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import @interface DAAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: DAPagesContainerDemo/DAAppDelegate.m ================================================ // // DAAppDelegate.m // DAPagesContainerDemo // // Created by Daria Kopaliani on 5/30/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import "DAAppDelegate.h" @implementation DAAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end ================================================ FILE: DAPagesContainerDemo/DAPagesContainerDemo-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier no.beat.apps.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0.1 CFBundleSignature ???? CFBundleVersion 1.0.1 LSRequiresIPhoneOS UIMainStoryboardFile MainStoryboard UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: DAPagesContainerDemo/DAPagesContainerDemo-Prefix.pch ================================================ // // Prefix header for all source files of the 'DAPagesContainerDemo' target in the 'DAPagesContainerDemo' project // #import #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __OBJC__ #import #import #endif ================================================ FILE: DAPagesContainerDemo/DAViewController.h ================================================ // // DAViewController.h // DAPagesContainerControllerDemo // // Created by Daria Kopaliani on 5/30/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import @interface DAViewController : UIViewController @end ================================================ FILE: DAPagesContainerDemo/DAViewController.m ================================================ // // DAViewController.m // DAPagesContainerDemo // // Created by Daria Kopaliani on 5/30/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import "DAViewController.h" #import "DAPagesContainer.h" @interface DAViewController () @property (strong, nonatomic) DAPagesContainer *pagesContainer; @end @implementation DAViewController - (void)viewDidLoad { [super viewDidLoad]; self.pagesContainer = [[DAPagesContainer alloc] init]; [self.pagesContainer willMoveToParentViewController:self]; self.pagesContainer.view.frame = self.view.bounds; self.pagesContainer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.view addSubview:self.pagesContainer.view]; [self.pagesContainer didMoveToParentViewController:self]; UIViewController *beaverViewController = [[UIViewController alloc] init]; UIImageView *beaverImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"beaver.jpg"]]; beaverImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [beaverViewController.view addSubview:beaverImageView]; beaverViewController.title = @"BEAVER"; UIViewController *buckDeerViewController = [[UIViewController alloc] init]; UIImageView *buckDeerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"buckDeer.jpg"]]; buckDeerImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [buckDeerViewController.view addSubview:buckDeerImageView]; buckDeerViewController.title = @"BUCK DEER"; UIViewController *catViewController = [[UIViewController alloc] init]; UIImageView *catImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cat.jpg"]]; catImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [catViewController.view addSubview:catImageView]; catViewController.title = @"CAT"; UIViewController *lionViewController = [[UIViewController alloc] init]; UIImageView *lionImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"lion.jpg"]]; lionImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [lionViewController.view addSubview:lionImageView]; lionViewController.title = @"REALLY CUTE LION"; self.pagesContainer.viewControllers = @[beaverViewController, buckDeerViewController, catViewController, lionViewController]; } - (void)viewWillUnload { self.pagesContainer = nil; [super viewWillUnload]; } - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [self.pagesContainer updateLayoutForNewOrientation:toInterfaceOrientation]; } @end ================================================ FILE: DAPagesContainerDemo/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: DAPagesContainerDemo/en.lproj/MainStoryboard.storyboard ================================================ ================================================ FILE: DAPagesContainerDemo/main.m ================================================ // // main.m // DAPagesContainerDemo // // Created by Daria Kopaliani on 5/30/13. // Copyright (c) 2013 Daria Kopaliani. All rights reserved. // #import #import "DAAppDelegate.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([DAAppDelegate class])); } } ================================================ FILE: DAPagesContainerDemo.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 43222CA61768E4590088986F /* DAPageIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 43222CA11768E4590088986F /* DAPageIndicatorView.m */; }; 43222CA71768E4590088986F /* DAPagesContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 43222CA31768E4590088986F /* DAPagesContainer.m */; }; 43222CA81768E4590088986F /* DAPagesContainerTopBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 43222CA51768E4590088986F /* DAPagesContainerTopBar.m */; }; 43222CAD1768E4770088986F /* DAAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 43222CAA1768E4770088986F /* DAAppDelegate.m */; }; 43222CAE1768E4770088986F /* DAViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 43222CAC1768E4770088986F /* DAViewController.m */; }; 43222CB61768E48E0088986F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 43222CB11768E48E0088986F /* Default-568h@2x.png */; }; 43222CB71768E48E0088986F /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 43222CB21768E48E0088986F /* Default.png */; }; 43222CB81768E48E0088986F /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 43222CB31768E48E0088986F /* Default@2x.png */; }; 43222CB91768E48E0088986F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 43222CB41768E48E0088986F /* main.m */; }; 43222CBE1768E49F0088986F /* beaver.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 43222CBA1768E49F0088986F /* beaver.jpg */; }; 43222CBF1768E49F0088986F /* buckDeer.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 43222CBB1768E49F0088986F /* buckDeer.jpg */; }; 43222CC01768E49F0088986F /* cat.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 43222CBC1768E49F0088986F /* cat.jpg */; }; 43222CC11768E49F0088986F /* lion.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 43222CBD1768E49F0088986F /* lion.jpg */; }; 43222CC61768E4D30088986F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 43222CC21768E4D30088986F /* InfoPlist.strings */; }; 43222CC71768E4D30088986F /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43222CC41768E4D30088986F /* MainStoryboard.storyboard */; }; 438BA887175785B00033EB2D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 438BA886175785B00033EB2D /* UIKit.framework */; }; 438BA88B175785B00033EB2D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 438BA88A175785B00033EB2D /* CoreGraphics.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 43222CA01768E4590088986F /* DAPageIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DAPageIndicatorView.h; path = DAPagesContainer/DAPageIndicatorView.h; sourceTree = ""; }; 43222CA11768E4590088986F /* DAPageIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DAPageIndicatorView.m; path = DAPagesContainer/DAPageIndicatorView.m; sourceTree = ""; }; 43222CA21768E4590088986F /* DAPagesContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DAPagesContainer.h; path = DAPagesContainer/DAPagesContainer.h; sourceTree = ""; }; 43222CA31768E4590088986F /* DAPagesContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DAPagesContainer.m; path = DAPagesContainer/DAPagesContainer.m; sourceTree = ""; }; 43222CA41768E4590088986F /* DAPagesContainerTopBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DAPagesContainerTopBar.h; path = DAPagesContainer/DAPagesContainerTopBar.h; sourceTree = ""; }; 43222CA51768E4590088986F /* DAPagesContainerTopBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DAPagesContainerTopBar.m; path = DAPagesContainer/DAPagesContainerTopBar.m; sourceTree = ""; }; 43222CA91768E4770088986F /* DAAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DAAppDelegate.h; path = DAPagesContainerDemo/DAAppDelegate.h; sourceTree = SOURCE_ROOT; }; 43222CAA1768E4770088986F /* DAAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DAAppDelegate.m; path = DAPagesContainerDemo/DAAppDelegate.m; sourceTree = SOURCE_ROOT; }; 43222CAB1768E4770088986F /* DAViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DAViewController.h; path = DAPagesContainerDemo/DAViewController.h; sourceTree = SOURCE_ROOT; }; 43222CAC1768E4770088986F /* DAViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DAViewController.m; path = DAPagesContainerDemo/DAViewController.m; sourceTree = SOURCE_ROOT; }; 43222CAF1768E48E0088986F /* DAPagesContainerDemo-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "DAPagesContainerDemo-Info.plist"; path = "DAPagesContainerDemo/DAPagesContainerDemo-Info.plist"; sourceTree = SOURCE_ROOT; }; 43222CB01768E48E0088986F /* DAPagesContainerDemo-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "DAPagesContainerDemo-Prefix.pch"; path = "DAPagesContainerDemo/DAPagesContainerDemo-Prefix.pch"; sourceTree = SOURCE_ROOT; }; 43222CB11768E48E0088986F /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "DAPagesContainerDemo/Default-568h@2x.png"; sourceTree = SOURCE_ROOT; }; 43222CB21768E48E0088986F /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = DAPagesContainerDemo/Default.png; sourceTree = SOURCE_ROOT; }; 43222CB31768E48E0088986F /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "DAPagesContainerDemo/Default@2x.png"; sourceTree = SOURCE_ROOT; }; 43222CB41768E48E0088986F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = DAPagesContainerDemo/main.m; sourceTree = SOURCE_ROOT; }; 43222CBA1768E49F0088986F /* beaver.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = beaver.jpg; path = DAPagesContainerDemo/Resources/beaver.jpg; sourceTree = SOURCE_ROOT; }; 43222CBB1768E49F0088986F /* buckDeer.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = buckDeer.jpg; path = DAPagesContainerDemo/Resources/buckDeer.jpg; sourceTree = SOURCE_ROOT; }; 43222CBC1768E49F0088986F /* cat.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = cat.jpg; path = DAPagesContainerDemo/Resources/cat.jpg; sourceTree = SOURCE_ROOT; }; 43222CBD1768E49F0088986F /* lion.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = lion.jpg; path = DAPagesContainerDemo/Resources/lion.jpg; sourceTree = SOURCE_ROOT; }; 43222CC31768E4D30088986F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = DAPagesContainerDemo/en.lproj/InfoPlist.strings; sourceTree = SOURCE_ROOT; }; 43222CC51768E4D30088986F /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = DAPagesContainerDemo/en.lproj/MainStoryboard.storyboard; sourceTree = SOURCE_ROOT; }; 438BA883175785B00033EB2D /* DAPagesContainerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DAPagesContainerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 438BA886175785B00033EB2D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 438BA888175785B00033EB2D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 438BA88A175785B00033EB2D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 438BA880175785B00033EB2D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 438BA887175785B00033EB2D /* UIKit.framework in Frameworks */, 438BA88B175785B00033EB2D /* CoreGraphics.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 438BA87A175785B00033EB2D = { isa = PBXGroup; children = ( 438BA8A9175785BB0033EB2D /* DAPagesContainer */, 438BA88C175785B00033EB2D /* DAPagesContainerDemo */, 438BA885175785B00033EB2D /* Frameworks */, 438BA884175785B00033EB2D /* Products */, ); sourceTree = ""; }; 438BA884175785B00033EB2D /* Products */ = { isa = PBXGroup; children = ( 438BA883175785B00033EB2D /* DAPagesContainerDemo.app */, ); name = Products; sourceTree = ""; }; 438BA885175785B00033EB2D /* Frameworks */ = { isa = PBXGroup; children = ( 438BA886175785B00033EB2D /* UIKit.framework */, 438BA888175785B00033EB2D /* Foundation.framework */, 438BA88A175785B00033EB2D /* CoreGraphics.framework */, ); name = Frameworks; sourceTree = ""; }; 438BA88C175785B00033EB2D /* DAPagesContainerDemo */ = { isa = PBXGroup; children = ( 43222CA91768E4770088986F /* DAAppDelegate.h */, 43222CAA1768E4770088986F /* DAAppDelegate.m */, 43222CAB1768E4770088986F /* DAViewController.h */, 43222CAC1768E4770088986F /* DAViewController.m */, 438BA88D175785B00033EB2D /* Supporting Files */, ); name = DAPagesContainerDemo; path = DAPagesContainerControllerDemo; sourceTree = ""; }; 438BA88D175785B00033EB2D /* Supporting Files */ = { isa = PBXGroup; children = ( 43222CC21768E4D30088986F /* InfoPlist.strings */, 43222CC41768E4D30088986F /* MainStoryboard.storyboard */, 43222CAF1768E48E0088986F /* DAPagesContainerDemo-Info.plist */, 43222CB01768E48E0088986F /* DAPagesContainerDemo-Prefix.pch */, 43222CB11768E48E0088986F /* Default-568h@2x.png */, 43222CB21768E48E0088986F /* Default.png */, 43222CB31768E48E0088986F /* Default@2x.png */, 43222CB41768E48E0088986F /* main.m */, 438BA8B6175786240033EB2D /* Images */, ); name = "Supporting Files"; sourceTree = ""; }; 438BA8A9175785BB0033EB2D /* DAPagesContainer */ = { isa = PBXGroup; children = ( 43222CA21768E4590088986F /* DAPagesContainer.h */, 43222CA31768E4590088986F /* DAPagesContainer.m */, 43222CA41768E4590088986F /* DAPagesContainerTopBar.h */, 43222CA51768E4590088986F /* DAPagesContainerTopBar.m */, 43222CA01768E4590088986F /* DAPageIndicatorView.h */, 43222CA11768E4590088986F /* DAPageIndicatorView.m */, ); name = DAPagesContainer; sourceTree = ""; }; 438BA8B6175786240033EB2D /* Images */ = { isa = PBXGroup; children = ( 43222CBA1768E49F0088986F /* beaver.jpg */, 43222CBB1768E49F0088986F /* buckDeer.jpg */, 43222CBC1768E49F0088986F /* cat.jpg */, 43222CBD1768E49F0088986F /* lion.jpg */, ); name = Images; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 438BA882175785B00033EB2D /* DAPagesContainerDemo */ = { isa = PBXNativeTarget; buildConfigurationList = 438BA8A6175785B10033EB2D /* Build configuration list for PBXNativeTarget "DAPagesContainerDemo" */; buildPhases = ( 438BA87F175785B00033EB2D /* Sources */, 438BA880175785B00033EB2D /* Frameworks */, 438BA881175785B00033EB2D /* Resources */, ); buildRules = ( ); dependencies = ( ); name = DAPagesContainerDemo; productName = DAPagesContainerControllerDemo; productReference = 438BA883175785B00033EB2D /* DAPagesContainerDemo.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 438BA87B175785B00033EB2D /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = DA; LastUpgradeCheck = 0460; ORGANIZATIONNAME = "Daria Kopaliani"; }; buildConfigurationList = 438BA87E175785B00033EB2D /* Build configuration list for PBXProject "DAPagesContainerDemo" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 438BA87A175785B00033EB2D; productRefGroup = 438BA884175785B00033EB2D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 438BA882175785B00033EB2D /* DAPagesContainerDemo */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 438BA881175785B00033EB2D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 43222CB61768E48E0088986F /* Default-568h@2x.png in Resources */, 43222CB71768E48E0088986F /* Default.png in Resources */, 43222CB81768E48E0088986F /* Default@2x.png in Resources */, 43222CBE1768E49F0088986F /* beaver.jpg in Resources */, 43222CBF1768E49F0088986F /* buckDeer.jpg in Resources */, 43222CC01768E49F0088986F /* cat.jpg in Resources */, 43222CC11768E49F0088986F /* lion.jpg in Resources */, 43222CC61768E4D30088986F /* InfoPlist.strings in Resources */, 43222CC71768E4D30088986F /* MainStoryboard.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 438BA87F175785B00033EB2D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 43222CA61768E4590088986F /* DAPageIndicatorView.m in Sources */, 43222CA71768E4590088986F /* DAPagesContainer.m in Sources */, 43222CA81768E4590088986F /* DAPagesContainerTopBar.m in Sources */, 43222CAD1768E4770088986F /* DAAppDelegate.m in Sources */, 43222CAE1768E4770088986F /* DAViewController.m in Sources */, 43222CB91768E48E0088986F /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 43222CC21768E4D30088986F /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 43222CC31768E4D30088986F /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; 43222CC41768E4D30088986F /* MainStoryboard.storyboard */ = { isa = PBXVariantGroup; children = ( 43222CC51768E4D30088986F /* en */, ); name = MainStoryboard.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 438BA8A4175785B10033EB2D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.1; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; 438BA8A5175785B10033EB2D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.1; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 438BA8A7175785B10033EB2D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "DAPagesContainerDemo/DAPagesContainerDemo-Prefix.pch"; INFOPLIST_FILE = "DAPagesContainerDemo/DAPagesContainerDemo-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; PRODUCT_NAME = DAPagesContainerDemo; WRAPPER_EXTENSION = app; }; name = Debug; }; 438BA8A8175785B10033EB2D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "DAPagesContainerDemo/DAPagesContainerDemo-Prefix.pch"; INFOPLIST_FILE = "DAPagesContainerDemo/DAPagesContainerDemo-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; PRODUCT_NAME = DAPagesContainerDemo; WRAPPER_EXTENSION = app; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 438BA87E175785B00033EB2D /* Build configuration list for PBXProject "DAPagesContainerDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( 438BA8A4175785B10033EB2D /* Debug */, 438BA8A5175785B10033EB2D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 438BA8A6175785B10033EB2D /* Build configuration list for PBXNativeTarget "DAPagesContainerDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( 438BA8A7175785B10033EB2D /* Debug */, 438BA8A8175785B10033EB2D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 438BA87B175785B00033EB2D /* Project object */; } ================================================ FILE: LICENSE ================================================ Copyright (c) 2012 Arthur Evstifeev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ DAPagesContainer ============== A generic views container with a scrollable top bar -------------- ![Alt text](DAPagesContainer.gif) Just pass the array of view controllers and DAPagesContainer will grab their titles and nicely display them in the top bar. The titles will be aligned properly even if their lengths differ and they do not fit the screen width. Features ============== - Both content scroll view and top bar can be used for navigation, - If a selected view is not the "nearest" neighbor of the current one, all the views in between will be skipped to provide a nice smooth animation, - Supports user interface orientation rotations, - Buttons in the top bar change their color as corresponding views become visible. Installation ============== CocoaPods -------------- Install CocoaPods if necessary: $ [sudo] gem install cocoapods $ pod setup Change to the directory of your Xcode project: $ cd /path/to/MyProject $ touch Podfile $ edit Podfile Edit your Podfile and add DAPagesContainer: platform :ios, '5.0' pod 'DAPagesContainer' Install into your Xcode project: $ pod install From now on open your project in Xcode from the .xcworkspace file (instead of the usual project file) Manual Install -------------- Just drag and drop DAPagesContainer folder into your project. Usage ============== Getting started with DAPagesContainer is really simple. Just alloc-init it and assign an array of view controllers. This can be done in the following way: self.pagesContainer = [[DAPagesContainer alloc] init]; [self.pagesContainer willMoveToParentViewController:self]; self.pagesContainer.view.frame = self.view.bounds; self.pagesContainer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.view addSubview:self.pagesContainer.view]; [self.pagesContainer didMoveToParentViewController:self]; self.pagesContainer.viewControllers = @[....]; Customization ============== Go ahead and experiment with these properties: @property (assign, nonatomic) NSUInteger topBarHeight; @property (assign, nonatomic) CGSize pageIndicatorViewSize; @property (strong, nonatomic) UIColor *topBarBackgroundColor; @property (strong, nonatomic) UIFont *topBarItemLabelsFont; @property (strong, nonatomic) UIColor *pageItemsTitleColor; @property (strong, nonatomic) UIColor *selectedPageItemColor; TODO ============== - Add colors customization for title views in top bar (not only shadows of grey)