diff --git a/README.md b/README.md index da1cbaf..8f14a1a 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,11 @@ Customization The most interesting customizable features are: -* Ability to skip intermediate view controllers when tapped on a "tab". +* Ability to skip intermediate view controllers when tapped on a "tab" (using `skipIntermediateViewControllers` property) * Indicators can be added at any position of the screen through storyboard layouts. -* Choose between progressive, non-progressive indicators. -* Add space padding between view controllers. +* Choose between progressive, non-progressive indicators (using isProgressiveIndicator property) +* Choose the alignment of the indicator as the user scrolls through tabs (using `barButtonView.selectedBarAlignment` property) +* Add space padding between view controllers @@ -90,6 +91,10 @@ Requirements Release Notes -------------- +Next Version + +* `selectedBarAlignment` added to `XLButtonBarView` + Version 2.0.0 * Added ability to change look and feel of selected tab. diff --git a/XLPagerTabStrip/XL/Controllers/XLButtonBarPagerTabStripViewController.m b/XLPagerTabStrip/XL/Controllers/XLButtonBarPagerTabStripViewController.m index ce2dd6d..2f68e88 100644 --- a/XLPagerTabStrip/XL/Controllers/XLButtonBarPagerTabStripViewController.m +++ b/XLPagerTabStrip/XL/Controllers/XLButtonBarPagerTabStripViewController.m @@ -83,7 +83,6 @@ -(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self.buttonBarView layoutIfNeeded]; [self.buttonBarView moveToIndex:self.currentIndex animated:NO swipeDirection:XLPagerTabStripDirectionNone pagerScroll:(self.isProgressiveIndicator ? XLPagerScrollYES :XLPagerScrollOnlyIfOutOfScreen)]; } diff --git a/XLPagerTabStrip/XL/Views/XLButtonBarView.h b/XLPagerTabStrip/XL/Views/XLButtonBarView.h index 96cd804..48d0e7f 100644 --- a/XLPagerTabStrip/XL/Views/XLButtonBarView.h +++ b/XLPagerTabStrip/XL/Views/XLButtonBarView.h @@ -33,10 +33,18 @@ typedef NS_ENUM(NSUInteger, XLPagerScroll) { XLPagerScrollOnlyIfOutOfScreen }; +typedef NS_ENUM(NSUInteger, XLSelectedBarAlignment) { + XLSelectedBarAlignmentLeft, + XLSelectedBarAlignmentCenter, + XLSelectedBarAlignmentRight, + XLSelectedBarAlignmentProgressive +}; + @interface XLButtonBarView : UICollectionView @property (readonly, nonatomic) UIView * selectedBar; @property (nonatomic) CGFloat selectedBarHeight; +@property (nonatomic) XLSelectedBarAlignment selectedBarAlignment; @property UIFont * labelFont; @property NSUInteger leftRightMargin; diff --git a/XLPagerTabStrip/XL/Views/XLButtonBarView.m b/XLPagerTabStrip/XL/Views/XLButtonBarView.m index cd15e88..679db2a 100644 --- a/XLPagerTabStrip/XL/Views/XLButtonBarView.m +++ b/XLPagerTabStrip/XL/Views/XLButtonBarView.m @@ -82,13 +82,14 @@ -(void)moveFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex withProgressPercentage:(CGFloat)progressPercentage pagerScroll:(XLPagerScroll)pagerScroll { + // First, calculate and set the frame of the 'selectedBar' + self.selectedOptionIndex = (progressPercentage > 0.5 ) ? toIndex : fromIndex; - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:fromIndex inSection:0]]; - CGRect fromFrame = attributes.frame; + CGRect fromFrame = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:fromIndex inSection:0]].frame; NSInteger numberOfItems = [self.dataSource collectionView:self numberOfItemsInSection:0]; CGRect toFrame; - if (toIndex < 0 || toIndex > [self.dataSource collectionView:self numberOfItemsInSection:0] - 1){ + if (toIndex < 0 || toIndex > numberOfItems - 1){ if (toIndex < 0) { UICollectionViewLayoutAttributes * cellAtts = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; toFrame = CGRectOffset(cellAtts.frame, -cellAtts.frame.size.width, 0); @@ -105,34 +106,52 @@ targetFrame.size.height = self.selectedBar.frame.size.height; targetFrame.size.width += (toFrame.size.width - fromFrame.size.width) * progressPercentage; targetFrame.origin.x += (toFrame.origin.x - fromFrame.origin.x) * progressPercentage; - NSUInteger offset = 35; - float xValue = 0; - if (self.contentSize.width > self.frame.size.width){ - xValue = MIN(self.contentSize.width - self.frame.size.width, targetFrame.origin.x - offset <= 0 ? 0 : targetFrame.origin.x - offset); - } - [self setContentOffset:CGPointMake(xValue, 0) animated:(ABS(self.contentOffset.x - xValue) > 30)]; + self.selectedBar.frame = CGRectMake(targetFrame.origin.x, self.selectedBar.frame.origin.y, targetFrame.size.width, self.selectedBar.frame.size.height); + + // Next, calculate and set the contentOffset of the UICollectionView + // (so it scrolls the selectedBar into the appriopriate place given the self.selectedBarAlignment) + + float targetContentOffset = 0; + // Only bother calculating the contentOffset if there are sufficient tabs that the bar can actually scroll! + if (self.contentSize.width > self.frame.size.width) + { + CGFloat toContentOffset = [self contentOffsetForCellWithFrame:toFrame index:toIndex]; + CGFloat fromContentOffset = [self contentOffsetForCellWithFrame:fromFrame index:fromIndex]; + + targetContentOffset = fromContentOffset + ((toContentOffset - fromContentOffset) * progressPercentage); + } + + // If there is a large difference between the current contentOffset and the contentOffset we're about to set + // then the change might be visually jarring so animate it. (This will likely occur if the user manually + // scrolled the XLButtonBarView and then subsequently scrolled the UIPageViewController) + // Alternatively if the fromIndex and toIndex are the same then this is the last call to this method in the + // progression so as a precaution always animate this contentOffest change + BOOL animated = (ABS(self.contentOffset.x - targetContentOffset) > 30) || (fromIndex == toIndex); + [self setContentOffset:CGPointMake(targetContentOffset, 0) animated:animated]; } -(void)updateSelectedBarPositionWithAnimation:(BOOL)animation swipeDirection:(XLPagerTabStripDirection __unused)swipeDirection pagerScroll:(XLPagerScroll)pagerScroll { - CGRect frame = self.selectedBar.frame; + CGRect selectedBarFrame = self.selectedBar.frame; - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedOptionIndex inSection:0]]; - CGRect cellFrame = attributes.frame; + NSIndexPath *selectedCellIndexPath = [NSIndexPath indexPathForItem:self.selectedOptionIndex inSection:0]; + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:selectedCellIndexPath]; + CGRect selectedCellFrame = attributes.frame; - [self updateContentOffsetAnimated:animation pagerScroll:pagerScroll toFrame:cellFrame]; + [self updateContentOffsetAnimated:animation pagerScroll:pagerScroll toFrame:selectedCellFrame toIndex:selectedCellIndexPath.row]; + + selectedBarFrame.size.width = selectedCellFrame.size.width; + selectedBarFrame.origin.x = selectedCellFrame.origin.x; - frame.size.width = cellFrame.size.width; - frame.origin.x = cellFrame.origin.x; if (animation){ [UIView animateWithDuration:0.3 animations:^{ - [self.selectedBar setFrame:frame]; + self.selectedBar.frame = selectedBarFrame; }]; } else{ - self.selectedBar.frame = frame; + self.selectedBar.frame = selectedBarFrame; } } @@ -140,40 +159,89 @@ #pragma mark - Helpers --(void)updateContentOffsetAnimated:(BOOL)animated pagerScroll:(XLPagerScroll)pageScroller toFrame:(CGRect)frame +- (void)updateContentOffsetAnimated:(BOOL)animated pagerScroll:(XLPagerScroll)pageScroller toFrame:(CGRect)selectedCellFrame toIndex:(NSUInteger)toIndex { - if (pageScroller != XLPagerScrollNO){ - CGFloat leftInset = ((UICollectionViewFlowLayout *)self.collectionViewLayout).sectionInset.left; - if (pageScroller == XLPagerScrollOnlyIfOutOfScreen){ - if (frame.origin.x >= self.contentOffset.x && frame.origin.x < (self.contentOffset.x + self.frame.size.width - leftInset)){ + if (pageScroller != XLPagerScrollNO) + { + if (pageScroller == XLPagerScrollOnlyIfOutOfScreen) + { + if ((selectedCellFrame.origin.x >= self.contentOffset.x) + && (selectedCellFrame.origin.x < (self.contentOffset.x + self.frame.size.width - self.contentInset.left))){ return; } } - NSUInteger offset = 30; - float xValue = MIN( - MAX(0, - self.collectionViewLayout.collectionViewContentSize.width - self.frame.size.width), // dont scroll if we are at the end of scroll view, if content is smaller than container width we scroll 0 - MAX(leftInset - frame.origin.x, frame.origin.x - leftInset - offset) - ); - [self setContentOffset:CGPointMake(xValue, 0) animated:animated]; + + CGFloat targetContentOffset = 0; + // Only bother calculating the contentOffset if there are sufficient tabs that the bar can actually scroll! + if (self.contentSize.width > self.frame.size.width) + { + targetContentOffset = [self contentOffsetForCellWithFrame:selectedCellFrame index:toIndex]; + } + + [self setContentOffset:CGPointMake(targetContentOffset, 0) animated:animated]; } } +- (CGFloat)contentOffsetForCellWithFrame:(CGRect)cellFrame index:(NSUInteger)index +{ + UIEdgeInsets sectionInset = ((UICollectionViewFlowLayout *)self.collectionViewLayout).sectionInset; + + CGFloat alignmentOffset = 0; + + switch (self.selectedBarAlignment) + { + case XLSelectedBarAlignmentLeft: + { + alignmentOffset = sectionInset.left; + break; + } + case XLSelectedBarAlignmentRight: + { + alignmentOffset = self.frame.size.width - sectionInset.right - cellFrame.size.width; + break; + } + case XLSelectedBarAlignmentCenter: + { + alignmentOffset = (self.frame.size.width - cellFrame.size.width) * 0.5; + break; + } + case XLSelectedBarAlignmentProgressive: + { + CGFloat cellHalfWidth = cellFrame.size.width * 0.5; + CGFloat leftAlignmentOffest = sectionInset.left + cellHalfWidth; + CGFloat rightAlignmentOffset = self.frame.size.width - sectionInset.right - cellHalfWidth; + NSInteger numberOfItems = [self.dataSource collectionView:self numberOfItemsInSection:0]; + CGFloat progress = index / (CGFloat)(numberOfItems - 1); + alignmentOffset = leftAlignmentOffest + ((rightAlignmentOffset - leftAlignmentOffest) * progress) - cellHalfWidth; + break; + } + } + + CGFloat contentOffset = cellFrame.origin.x - alignmentOffset; + + // Ensure that the contentOffset wouldn't scroll the UICollectioView passed the beginning + contentOffset = MAX(0, contentOffset); + // Ensure that the contentOffset wouldn't scroll the UICollectioView passed the end + contentOffset = MIN(self.contentSize.width - self.frame.size.width, contentOffset); + + return contentOffset; +} #pragma mark - Properties --(void)setSelectedBarHeight:(CGFloat)selectedBarHeight +- (void)setSelectedBarHeight:(CGFloat)selectedBarHeight { _selectedBarHeight = selectedBarHeight; _selectedBar.frame = CGRectMake(_selectedBar.frame.origin.x, self.frame.size.height - _selectedBarHeight, _selectedBar.frame.size.width, _selectedBarHeight); } --(UIView *)selectedBar +- (UIView *)selectedBar { - if (_selectedBar) return _selectedBar; - _selectedBar = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - _selectedBarHeight, 0, _selectedBarHeight)]; - _selectedBar.layer.zPosition = 9999; - _selectedBar.backgroundColor = [UIColor blackColor]; + if (!_selectedBar) { + _selectedBar = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - _selectedBarHeight, 0, _selectedBarHeight)]; + _selectedBar.layer.zPosition = 9999; + _selectedBar.backgroundColor = [UIColor blackColor]; + } return _selectedBar; }