diff --git a/Example.xcodeproj/project.pbxproj b/Example.xcodeproj/project.pbxproj index ef5d985..e7c1232 100644 --- a/Example.xcodeproj/project.pbxproj +++ b/Example.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 287D0A7E1C4B7B55004566D6 /* XLPagerTabStrip.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 287D0A7A1C4B7B26004566D6 /* XLPagerTabStrip.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 28F828D01C4B714D00330CF4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F828CF1C4B714D00330CF4 /* AppDelegate.swift */; }; 28F828DA1C4B714D00330CF4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 28F828D81C4B714D00330CF4 /* LaunchScreen.storyboard */; }; + CB3697BF1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB3697BE1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift */; }; CB71C6EB1C4EB964008EC806 /* SegmentedExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB71C6EA1C4EB964008EC806 /* SegmentedExampleViewController.swift */; }; CB71C6F31C4FDDCE008EC806 /* PostCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB71C6F21C4FDDCE008EC806 /* PostCell.swift */; }; CB86ED801C4D6F0D00DA463B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CB86ED6F1C4D6F0D00DA463B /* Assets.xcassets */; }; @@ -23,6 +24,7 @@ CBA0A1FF1C50304500C5748C /* BarExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA0A1FE1C50304500C5748C /* BarExampleViewController.swift */; }; CBA0A2021C5032E100C5748C /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBA0A2011C5032E100C5748C /* Storyboard.storyboard */; }; CBA0A2041C5033B400C5748C /* ReloadExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA0A2031C5033B400C5748C /* ReloadExampleViewController.swift */; }; + CBBD435F1C5274AE001A748E /* NavButtonBarExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBD435E1C5274AE001A748E /* NavButtonBarExampleViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -79,6 +81,7 @@ 28F828DB1C4B714D00330CF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Example/Info.plist; sourceTree = ""; }; 28F828E01C4B714D00330CF4 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28F828E61C4B714D00330CF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Example/ExampleUITests/Info.plist; sourceTree = ""; }; + CB3697BE1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ButtonBarExampleViewController.swift; path = Example/Example/ButtonBarExampleViewController.swift; sourceTree = SOURCE_ROOT; }; CB71C6EA1C4EB964008EC806 /* SegmentedExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SegmentedExampleViewController.swift; path = Example/SegmentedExampleViewController.swift; sourceTree = ""; }; CB71C6F21C4FDDCE008EC806 /* PostCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PostCell.swift; path = Example/PostCell.swift; sourceTree = ""; }; CB86ED6F1C4D6F0D00DA463B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Example/Assets.xcassets; sourceTree = ""; }; @@ -90,6 +93,7 @@ CBA0A1FE1C50304500C5748C /* BarExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BarExampleViewController.swift; path = Example/BarExampleViewController.swift; sourceTree = ""; }; CBA0A2011C5032E100C5748C /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = ""; }; CBA0A2031C5033B400C5748C /* ReloadExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ReloadExampleViewController.swift; path = Example/ReloadExampleViewController.swift; sourceTree = ""; }; + CBBD435E1C5274AE001A748E /* NavButtonBarExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NavButtonBarExampleViewController.swift; path = Example/NavButtonBarExampleViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -170,8 +174,10 @@ CB86ED891C4D712200DA463B /* ChildExampleViewController.swift */, CB86ED8D1C4D7A1800DA463B /* TableChildExampleViewController.swift */, CB71C6EA1C4EB964008EC806 /* SegmentedExampleViewController.swift */, + CB3697BE1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift */, CBA0A1FE1C50304500C5748C /* BarExampleViewController.swift */, CBA0A2031C5033B400C5748C /* ReloadExampleViewController.swift */, + CBBD435E1C5274AE001A748E /* NavButtonBarExampleViewController.swift */, ); name = Demo; sourceTree = ""; @@ -317,7 +323,9 @@ CB71C6EB1C4EB964008EC806 /* SegmentedExampleViewController.swift in Sources */, 28F828D01C4B714D00330CF4 /* AppDelegate.swift in Sources */, CBA0A2041C5033B400C5748C /* ReloadExampleViewController.swift in Sources */, + CB3697BF1C5177B4001FC5F8 /* ButtonBarExampleViewController.swift in Sources */, CBA0A1FF1C50304500C5748C /* BarExampleViewController.swift in Sources */, + CBBD435F1C5274AE001A748E /* NavButtonBarExampleViewController.swift in Sources */, CB86ED8A1C4D712200DA463B /* ChildExampleViewController.swift in Sources */, CB86ED8E1C4D7A1800DA463B /* TableChildExampleViewController.swift in Sources */, CB71C6F31C4FDDCE008EC806 /* PostCell.swift in Sources */, diff --git a/Example/Example/ButtonBarExampleViewController.swift b/Example/Example/ButtonBarExampleViewController.swift new file mode 100644 index 0000000..5f015c9 --- /dev/null +++ b/Example/Example/ButtonBarExampleViewController.swift @@ -0,0 +1,62 @@ +// +// ButtonBarExampleViewController.swift +// Example +// +// Created by Santiago on 1/21/16. +// +// + +import Foundation +import XLPagerTabStrip + +public class ButtonBarExampleViewControlle: ButtonBarPagerTabStripViewController { + var isReload = false + + public override func viewDidLoad() { + super.viewDidLoad() + + if pagerOptions.contains(.IsProgressiveIndicator) { + pagerOptions = pagerOptions.remove(.IsProgressiveIndicator)! + } + + buttonBarView.selectedBar.backgroundColor = .orangeColor() + buttonBarView.backgroundColor = UIColor(red: 7/255, green: 185/255, blue: 155/255, alpha: 1) + } + + // MARK: - PagerTabStripVIewControllerDataSource + + public override func childViewControllersForPagerTabStripViewController(pagerTabStripController: PagerTabStripViewController) -> [UIViewController] { + let child_1 = TableChildExampleViewController(style: .Plain) + let child_2 = ChildExampleViewController() + let child_3 = TableChildExampleViewController(style: .Grouped) + let child_4 = ChildExampleViewController() + let child_5 = TableChildExampleViewController(style: .Plain) + let child_6 = ChildExampleViewController() + let child_7 = TableChildExampleViewController(style: .Grouped) + let child_8 = ChildExampleViewController() + + guard isReload else { + return [child_1, child_2, child_3, child_4, child_5, child_6, child_7, child_8] + } + + var childViewControllers = [child_1, child_2, child_3, child_4, child_6, child_7, child_8] as Array + let count = childViewControllers.count + + for (index, _) in childViewControllers.enumerate(){ + let nElements = count - index + let n = (Int(arc4random()) % nElements) + index + if n != index{ + swap(&childViewControllers[index], &childViewControllers[n]) + } + } + let nItems = 1 + (rand() % 8) + return Array(childViewControllers.prefix(Int(nItems))) + } + + public override func reloadPagerTabStripView() { + isReload = true + pagerOptions = rand() % 2 == 0 ? pagerOptions.union(.IsProgressiveIndicator) : (pagerOptions.remove(.IsProgressiveIndicator) ?? pagerOptions) + pagerOptions = rand() % 2 == 0 ? pagerOptions.union(.IsElasticIndicatorLimit) : (pagerOptions.remove(.IsElasticIndicatorLimit) ?? pagerOptions) + super.reloadPagerTabStripView() + } +} \ No newline at end of file diff --git a/Example/Example/NavButtonBarExampleViewController.swift b/Example/Example/NavButtonBarExampleViewController.swift new file mode 100644 index 0000000..8ecdc6f --- /dev/null +++ b/Example/Example/NavButtonBarExampleViewController.swift @@ -0,0 +1,83 @@ +// +// NavButtonBarExampleViewController.swift +// Example +// +// Created by Santiago on 1/22/16. +// +// + +import Foundation +import XLPagerTabStrip + +public class NavButtonBarExampleViewController: ButtonBarPagerTabStripViewController { + var isReload = false + + public override func viewDidLoad() { + super.viewDidLoad() + + if pagerOptions.contains(.IsProgressiveIndicator) { + pagerOptions = pagerOptions.remove(.IsProgressiveIndicator)! + } + + buttonBarView.backgroundColor = .clearColor() + buttonBarView.selectedBar.backgroundColor = .orangeColor() + buttonBarView.removeFromSuperview() + navigationController?.navigationBar.addSubview(buttonBarView) + + changeCurrentIndexProgressive = { (oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: Float, changeCurrentIndex: Bool, animated: Bool) -> Void in + guard changeCurrentIndex == true else { return } + + oldCell?.label.textColor = UIColor(white: 1, alpha: 0.6) + newCell?.label.textColor = .whiteColor() + + if animated { + UIView.animateWithDuration(0.1, animations: { () -> Void in + newCell?.transform = CGAffineTransformMakeScale(1.0, 1.0) + oldCell?.transform = CGAffineTransformMakeScale(0.8, 0.8) + }) + } + else { + newCell?.transform = CGAffineTransformMakeScale(1.0, 1.0) + oldCell?.transform = CGAffineTransformMakeScale(0.8, 0.8) + } + } + } + + // MARK: - PagerTabStripViewControllerDataSource + + public override func childViewControllersForPagerTabStripViewController(pagerTabStripController: PagerTabStripViewController) -> [UIViewController] { + let child_1 = TableChildExampleViewController(style: .Plain) + let child_2 = ChildExampleViewController() + let child_3 = TableChildExampleViewController(style: .Grouped) + let child_4 = ChildExampleViewController() + let child_5 = TableChildExampleViewController(style: .Plain) + let child_6 = ChildExampleViewController() + let child_7 = TableChildExampleViewController(style: .Grouped) + let child_8 = ChildExampleViewController() + + guard isReload else { + return [child_1, child_2, child_3, child_4, child_5, child_6, child_7, child_8] + } + + var childViewControllers = [child_1, child_2, child_3, child_4, child_6, child_7, child_8] as Array + let count = childViewControllers.count + + for (index, _) in childViewControllers.enumerate(){ + let nElements = count - index + let n = (Int(arc4random()) % nElements) + index + if n != index{ + swap(&childViewControllers[index], &childViewControllers[n]) + } + } + let nItems = 1 + (rand() % 8) + return Array(childViewControllers.prefix(Int(nItems))) + } + + public override func reloadPagerTabStripView() { + isReload = true + pagerOptions = rand() % 2 == 0 ? pagerOptions.union(.IsProgressiveIndicator) : (pagerOptions.remove(.IsProgressiveIndicator) ?? pagerOptions) + pagerOptions = rand() % 2 == 0 ? pagerOptions.union(.IsElasticIndicatorLimit) : (pagerOptions.remove(.IsElasticIndicatorLimit) ?? pagerOptions) + super.reloadPagerTabStripView() + } + +} diff --git a/Example/Example/ReloadExampleViewController.swift b/Example/Example/ReloadExampleViewController.swift index f0f3f60..3647546 100644 --- a/Example/Example/ReloadExampleViewController.swift +++ b/Example/Example/ReloadExampleViewController.swift @@ -10,7 +10,10 @@ import UIKit import XLPagerTabStrip public class ReloadExampleViewController: UIViewController { - @IBOutlet public var titleLabel: UILabel! + @IBOutlet lazy public var titleLabel: UILabel! = { + let label = UILabel() + return label + }() public lazy var bigLabel: UILabel = { let bigLabel = UILabel() diff --git a/Example/Storyboard.storyboard b/Example/Storyboard.storyboard index 3797f8f..7be554d 100644 --- a/Example/Storyboard.storyboard +++ b/Example/Storyboard.storyboard @@ -135,17 +135,17 @@ - + - + - + @@ -230,6 +230,8 @@ + + @@ -284,19 +286,19 @@ - + - + - + - + @@ -309,15 +311,15 @@ - + - - + @@ -353,7 +355,7 @@ - + @@ -455,17 +457,17 @@ - + - + - + diff --git a/Sources/ButtonBarPagerTabStripViewController.swift b/Sources/ButtonBarPagerTabStripViewController.swift new file mode 100644 index 0000000..b443dc3 --- /dev/null +++ b/Sources/ButtonBarPagerTabStripViewController.swift @@ -0,0 +1,315 @@ +// ButtonBarPagerTabStripViewController.swift +// XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip ) +// +// Copyright (c) 2016 Xmartlabs ( http://xmartlabs.com ) +// +// +// 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. + +import Foundation + +public class ButtonBarPagerTabStripViewController: PagerTabStripViewController, UICollectionViewDelegate, UICollectionViewDataSource { + + public var changeCurrentIndex: ((oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, animated: Bool) -> Void)? = { _ in } + public var changeCurrentIndexProgressive: ((oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: Float, changeCurrentIndex: Bool, animated: Bool) -> Void)? = { _ in } + + @IBOutlet public lazy var buttonBarView: ButtonBarView! = { [unowned self] in + var flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .Horizontal + flowLayout.sectionInset = UIEdgeInsetsMake(0, 35, 0, 35) + + let buttonBar: ButtonBarView = ButtonBarView(frame: CGRectMake(0, 0, self.view.frame.size.width, 44), collectionViewLayout: flowLayout) + buttonBar.backgroundColor = .orangeColor() + buttonBar.selectedBar.backgroundColor = .blackColor() + buttonBar.autoresizingMask = .FlexibleWidth + + var bundle = NSBundle(forClass: ButtonBarView.self) + if let url = bundle.URLForResource("XLPagerTabStrip", withExtension: "bundle") { + bundle = NSBundle(URL: url)! + } + + buttonBar.registerNib(UINib(nibName: "ButtonCell", bundle: bundle), forCellWithReuseIdentifier: "Cell") + var newContainerViewFrame = self.containerView.frame + newContainerViewFrame.origin.y = 44 + newContainerViewFrame.size.height = self.containerView.frame.size.height - (44 - self.containerView.frame.origin.y) + self.containerView.frame = newContainerViewFrame + return buttonBar + }() + + lazy private var cachedCellWidths: [CGFloat]? = { [unowned self] in + return self.calculateWidths() + }() + + private var shouldUpdateButtonBarView = true + private var isViewAppearing = false + private var isViewRotating = false + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + public override func viewDidLoad() { + super.viewDidLoad() + + if buttonBarView.superview == nil { + view.addSubview(buttonBarView) + } + if buttonBarView.delegate == nil { + buttonBarView.delegate = self + } + if buttonBarView.dataSource == nil { + buttonBarView.dataSource = self + } + + buttonBarView.labelFont = UIFont.boldSystemFontOfSize(18) + buttonBarView.leftRightMargin = 8 + buttonBarView.scrollsToTop = false + let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout + flowLayout.scrollDirection = .Horizontal + buttonBarView.showsHorizontalScrollIndicator = false + } + + public override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + buttonBarView.layoutIfNeeded() + isViewAppearing = true + } + + public override func viewDidAppear(animated: Bool) { + super.viewDidAppear(animated) + isViewAppearing = true + } + + public override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + guard isViewAppearing || isViewRotating else { return } + + cachedCellWidths = calculateWidths() + let flowLayout = buttonBarView.collectionViewLayout + flowLayout.invalidateLayout() + + buttonBarView.layoutIfNeeded() + +// buttonBarView.moveToIndex(currentIndex, animated: false, swipeDirection: .None, pagerScroll: .ScrollOnlyIfOutOfScreen) + } + + // MARK: - View Rotation + + public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) + + isViewRotating = true + coordinator.animateAlongsideTransition(nil) { (context) -> Void in + self.isViewRotating = false + } + } + + public override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) { + super.willRotateToInterfaceOrientation(toInterfaceOrientation, duration: duration) + isViewRotating = true + } + + // MARK: - Public Methods + + public override func reloadPagerTabStripView() { + super.reloadPagerTabStripView() + + if isViewLoaded() { + buttonBarView.reloadData() + cachedCellWidths = calculateWidths() + buttonBarView.moveToIndex(currentIndex, animated: false, swipeDirection: .None, pagerScroll: .YES) + } + } + + public func calculateStretchedCellWidths(minimumCellWidths: Array, suggestedStretchedCellWidth: CGFloat, previousNumberOfLargeCells: Int) -> CGFloat { + var numberOfLargeCells = 0 + var totalWidthOfLargeCells: CGFloat = 0 + + for minimumCellWidthValue in minimumCellWidths { + let minimumCellWidth = minimumCellWidthValue.floatValue + if CGFloat(minimumCellWidth) > suggestedStretchedCellWidth { + totalWidthOfLargeCells += CGFloat(minimumCellWidth) + numberOfLargeCells++ + } + } + + guard numberOfLargeCells > previousNumberOfLargeCells else { return suggestedStretchedCellWidth } + + let flowLayout = buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout + let collectionViewAvailiableWidth = buttonBarView.frame.size.width - flowLayout.sectionInset.left - flowLayout.sectionInset.right + let numberOfCells = minimumCellWidths.count + let cellSpacingTotal = CGFloat(numberOfCells - 1) * flowLayout.minimumInteritemSpacing + + let numberOfSmallCells = numberOfCells - numberOfLargeCells + let newSuggestedStretchedCellWidth = (collectionViewAvailiableWidth - totalWidthOfLargeCells - cellSpacingTotal) / CGFloat(numberOfSmallCells) + + return calculateStretchedCellWidths(minimumCellWidths, suggestedStretchedCellWidth: newSuggestedStretchedCellWidth, previousNumberOfLargeCells: numberOfLargeCells) + } + + public override func pagerTabStripViewController(pagerTabStripViewController: PagerTabStripViewController, updateIndicatorFromIndex fromIndex: Int, toIndex: Int) throws { + guard shouldUpdateButtonBarView else { return } + + let direction: SwipeDirection = (toIndex < fromIndex) ? .Right : .Left + buttonBarView.moveToIndex(toIndex, animated: true, swipeDirection: direction, pagerScroll: .YES) + if let changeCurrentIndex = changeCurrentIndex { + let oldCell = buttonBarView.cellForItemAtIndexPath(NSIndexPath(forItem: currentIndex != fromIndex ? fromIndex : toIndex, inSection: 0)) as? ButtonBarViewCell + let newCell = buttonBarView.cellForItemAtIndexPath(NSIndexPath(forItem: currentIndex, inSection: 0)) as? ButtonBarViewCell + changeCurrentIndex(oldCell: oldCell, newCell: newCell, animated: true) + } + } + + public override func pagerTabStripViewController(pagerTabStripViewController: PagerTabStripViewController, updateIndicatorFromIndex fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: Float, indexWasChanged: Bool) throws { + guard shouldUpdateButtonBarView else { return } + + buttonBarView.moveFromIndex(fromIndex, toIndex: toIndex, progressPercentage: progressPercentage, pagerScroll: .YES) + if let changeCurrentIndexProgressive = changeCurrentIndexProgressive { + let oldCell = buttonBarView.cellForItemAtIndexPath(NSIndexPath(forItem: currentIndex != fromIndex ? fromIndex : toIndex, inSection: 0)) as? ButtonBarViewCell + let newCell = buttonBarView.cellForItemAtIndexPath(NSIndexPath(forItem: currentIndex, inSection: 0)) as? ButtonBarViewCell + changeCurrentIndexProgressive(oldCell: oldCell, newCell: newCell, progressPercentage: progressPercentage, changeCurrentIndex: indexWasChanged, animated: true) + } + } + + // MARK: - UICollectionViewDelegateFlowLayut + + public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { + guard cachedCellWidths?.count > indexPath.row else { return CGSizeZero } + + if let cellWidthValue = cachedCellWidths?[indexPath.row] { + return CGSizeMake(cellWidthValue, collectionView.frame.size.height) + } + + return CGSizeZero + } + + public func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { + guard indexPath.item != currentIndex else { return } + + buttonBarView.moveToIndex(indexPath.item, animated: true, swipeDirection: .None, pagerScroll: .YES) + shouldUpdateButtonBarView = false + + let oldCell = buttonBarView.cellForItemAtIndexPath(NSIndexPath(forItem: currentIndex, inSection: 0)) as! ButtonBarViewCell + let newCell = buttonBarView.cellForItemAtIndexPath(NSIndexPath(forItem: indexPath.item, inSection: 0)) as! ButtonBarViewCell + if pagerOptions.contains(.IsProgressiveIndicator) { + if let changeCurrentIndexProgressive = changeCurrentIndexProgressive { + changeCurrentIndexProgressive(oldCell: oldCell, newCell: newCell, progressPercentage: 1, changeCurrentIndex: true, animated: true) + } + } + else { + if let changeCurrentIndex = changeCurrentIndex { + changeCurrentIndex(oldCell: oldCell, newCell: newCell, animated: true) + } + } + + moveToViewControllerAtIndex(indexPath.item) + } + + // MARK: - UICollectionViewDataSource + + public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return viewControllers.count + } + + public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as? ButtonBarViewCell else { + fatalError("UICollectionViewCell should be or extend XLButtonBarViewCell") + } + + let childController = viewControllers[indexPath.item] as? PagerTabStripChildItem + + cell.label.text = childController?.childHeaderForPagerTabStripViewController(self).title + if let image = childController?.childHeaderForPagerTabStripViewController(self).image { + cell.imageView.image = image + } + + if let highlightedImage = childController?.childHeaderForPagerTabStripViewController(self).highlightedImage { + cell.imageView.highlightedImage = highlightedImage + } + + if pagerOptions.contains(.IsProgressiveIndicator) { + if let changeCurrentIndexProgressive = changeCurrentIndexProgressive { + changeCurrentIndexProgressive(oldCell: currentIndex == indexPath.item ? nil : cell, newCell: currentIndex == indexPath.item ? cell : nil, progressPercentage: 1, changeCurrentIndex: true, animated: false) + } + } + else { + if let changeCurrentIndex = changeCurrentIndex { + changeCurrentIndex(oldCell: currentIndex == indexPath.item ? nil : cell, newCell: currentIndex == indexPath.item ? cell : nil, animated: false) + } + } + + return cell + } + + // MARK: - UIScrollViewDelegate + + public override func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) { + super.scrollViewDidEndScrollingAnimation(scrollView) + + guard scrollView == containerView else { return } + shouldUpdateButtonBarView = true + } + + private func calculateWidths() -> [CGFloat] { + let flowLayout = self.buttonBarView.collectionViewLayout as! UICollectionViewFlowLayout + let numberOfCells = self.viewControllers.count + + var minimumCellWidths = [CGFloat]() + var collectionViewContentWidth: CGFloat = 0 + + for viewController in self.viewControllers { + let childController = viewController as? PagerTabStripChildItem + + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = self.buttonBarView.labelFont! + label.text = childController?.childHeaderForPagerTabStripViewController(self).title + let labelSize = label.intrinsicContentSize() + + let minimumCellWidth = labelSize.width + CGFloat(self.buttonBarView.leftRightMargin! * 2) + minimumCellWidths.append(minimumCellWidth) + + collectionViewContentWidth += minimumCellWidth + } + + let cellSpacingTotal = CGFloat(numberOfCells - 1) * flowLayout.minimumInteritemSpacing + collectionViewContentWidth += cellSpacingTotal + + let collectionViewAvailableVisibleWidth = self.buttonBarView.frame.size.width - flowLayout.sectionInset.left - flowLayout.sectionInset.right + + if self.buttonBarView.shouldCellsFillAvailiableWidth || collectionViewAvailableVisibleWidth < collectionViewContentWidth { + return minimumCellWidths + } + else { + let stretchedCellWidthIfAllEqual = (collectionViewAvailableVisibleWidth - cellSpacingTotal) / CGFloat(numberOfCells) + let generalMinimumCellWidth = self.calculateStretchedCellWidths(minimumCellWidths, suggestedStretchedCellWidth: stretchedCellWidthIfAllEqual, previousNumberOfLargeCells: 0) + var stretchedCellWidths = [CGFloat]() + + for minimumCellWidthValue in minimumCellWidths { + let cellWidth = (minimumCellWidthValue > generalMinimumCellWidth) ? minimumCellWidthValue : generalMinimumCellWidth + stretchedCellWidths.append(cellWidth) + } + + return stretchedCellWidths + } + } +} diff --git a/Sources/ButtonBarView.swift b/Sources/ButtonBarView.swift new file mode 100644 index 0000000..0163b9e --- /dev/null +++ b/Sources/ButtonBarView.swift @@ -0,0 +1,170 @@ +// ButtonBarView.swift +// XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip ) +// +// Copyright (c) 2016 Xmartlabs ( http://xmartlabs.com ) +// +// +// 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. + +import UIKit + +public enum PagerScroll: Int { + case NO = 1, YES = 2, ScrollOnlyIfOutOfScreen = 3 +} + +public enum SelectedBarAlignment: Int { + case Left = 1, Center = 2, Right = 3, Progressive = 4 +} + +public class ButtonBarView: UICollectionView { + + public lazy var selectedBar: UIView = { [unowned self] in + let bar = UIView(frame: CGRectMake(0, self.frame.size.height - CGFloat(self.selectedBarHeight), 0, CGFloat(self.selectedBarHeight))) + bar.layer.zPosition = 9999 + bar.backgroundColor = .blackColor() + return bar + }() + + public var shouldCellsFillAvailiableWidth = true + var selectedBarHeight = 5 + var selectedBarAlignment: SelectedBarAlignment = .Center + var labelFont: UIFont? = nil + var leftRightMargin: Int? = nil + var selectedIndex = 0 + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + addSubview(selectedBar) + } + + override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + addSubview(selectedBar) + } + + public func moveToIndex(toIndex: Int, animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) -> Void { + selectedIndex = toIndex + updateSelectedBarPosition(animated, swipeDirection: swipeDirection, pagerScroll: pagerScroll) + } + + public func moveFromIndex(fromIndex: Int, toIndex: Int, progressPercentage: Float,pagerScroll: PagerScroll) -> Void { + selectedIndex = (progressPercentage > 0.5) ? toIndex : fromIndex + + let fromFrame = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: fromIndex, inSection: 0))!.frame + let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0) + var toFrame: CGRect + + if toIndex < 0 || toIndex > numberOfItems - 1 { + if toIndex < 0 { + let cellAtts = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: 0, inSection: 0)) + toFrame = CGRectOffset(cellAtts!.frame, -cellAtts!.frame.size.width, 0) + } + else { + let cellAtts = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: (numberOfItems - 1), inSection: 0)) + toFrame = CGRectOffset(cellAtts!.frame, cellAtts!.frame.size.width, 0) + } + } + else { + toFrame = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: toIndex, inSection: 0))!.frame + } + + var targetFrame = fromFrame + targetFrame.size.height = selectedBar.frame.size.height + targetFrame.size.width += (toFrame.size.width - fromFrame.size.width) * CGFloat(progressPercentage) + targetFrame.origin.x += (toFrame.origin.x - fromFrame.origin.x) * CGFloat(progressPercentage) + + selectedBar.frame = CGRectMake(targetFrame.origin.x, selectedBar.frame.origin.y, targetFrame.size.width, selectedBar.frame.size.height) + + var targetContentOffset: CGFloat = 0.0 + if contentSize.width > frame.size.width { + let toContentOffset = contentOffsetForCell(withFrame: toFrame, andIndex: toIndex) + let fromContentOffset = contentOffsetForCell(withFrame: fromFrame, andIndex: fromIndex) + + targetContentOffset = fromContentOffset + ((toContentOffset - fromContentOffset) * CGFloat(progressPercentage)) + } + + let animated = abs(contentOffset.x - targetContentOffset) > 30 || (fromIndex == toIndex) + setContentOffset(CGPointMake(targetContentOffset, 0), animated: animated) + } + + public func updateSelectedBarPosition(animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) -> Void { + var selectedBarFrame = selectedBar.frame + + let selectedCellIndexPath = NSIndexPath(forItem: selectedIndex, inSection: 0) + let attributes = layoutAttributesForItemAtIndexPath(selectedCellIndexPath) + let selectedCellFrame = attributes!.frame + + updateContentOffset(animated, pagerScroll: pagerScroll, toFrame: selectedCellFrame, toIndex: selectedCellIndexPath.row) + + selectedBarFrame.size.width = selectedCellFrame.size.width + selectedBarFrame.origin.x = selectedCellFrame.origin.x + + if animated { + UIView.animateWithDuration(0.3, animations: { [weak self] in + self?.selectedBar.frame = selectedBarFrame + }) + } + else { + selectedBar.frame = selectedBarFrame + } + } + + // MARK: - Helpers + + private func updateContentOffset(animated: Bool, pagerScroll: PagerScroll, toFrame: CGRect, toIndex: Int) -> Void { + guard pagerScroll != .NO else { + return + } + + if pagerScroll == .ScrollOnlyIfOutOfScreen { + if (toFrame.origin.x >= contentOffset.x) && (toFrame.origin.x < (contentOffset.x + frame.size.width - contentInset.left)) { + return + } + } + + let targetContentOffset = contentSize.width > frame.size.width ? contentOffsetForCell(withFrame: toFrame, andIndex: toIndex) : 0 + setContentOffset(CGPointMake(targetContentOffset, 0), animated: animated) + } + + private func contentOffsetForCell(withFrame cellFrame: CGRect, andIndex index: Int) -> CGFloat { + let sectionInset = (collectionViewLayout as? UICollectionViewFlowLayout)!.sectionInset + var alignmentOffset: CGFloat = 0.0 + + switch selectedBarAlignment { + case .Left: + alignmentOffset = sectionInset.left + case .Right: + alignmentOffset = frame.size.width - sectionInset.right - cellFrame.size.width + case .Center: + alignmentOffset = (frame.size.width - cellFrame.size.width) * 0.5 + case .Progressive: + let cellHalfWidth = cellFrame.size.width * 0.5 + let leftAlignmentOffset = sectionInset.left + cellHalfWidth + let rightAlignmentOffset = frame.size.width - sectionInset.right - cellHalfWidth + let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0) + let progress = index / (numberOfItems - 1) + alignmentOffset = leftAlignmentOffset + (rightAlignmentOffset - leftAlignmentOffset) * CGFloat(progress) - cellHalfWidth + } + + var contentOffset = cellFrame.origin.x - alignmentOffset + contentOffset = max(0, contentOffset) + contentOffset = min(contentSize.width - frame.size.width, contentOffset) + return contentOffset + } +} diff --git a/Sources/ButtonBarViewCell.swift b/Sources/ButtonBarViewCell.swift new file mode 100644 index 0000000..224c6e5 --- /dev/null +++ b/Sources/ButtonBarViewCell.swift @@ -0,0 +1,45 @@ +// ButtonBarViewCell.swift +// XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip ) +// +// Copyright (c) 2016 Xmartlabs ( http://xmartlabs.com ) +// +// +// 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. + +import Foundation + +public class ButtonBarViewCell: UICollectionViewCell { + + @IBOutlet public var imageView: UIImageView! + @IBOutlet public lazy var label: UILabel! = { [unowned self] in + let label = UILabel(frame: self.contentView.bounds) + label.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] + label.textAlignment = .Center + label.font = UIFont.systemFontOfSize(14.0, weight: UIFontWeightMedium) + return label + }() + + public override func willMoveToSuperview(newSuperview: UIView?) { + super.willMoveToSuperview(newSuperview) + + if label.superview != nil { + contentView.addSubview(label) + } + } +} diff --git a/Sources/ButtonCell.xib b/Sources/ButtonCell.xib new file mode 100644 index 0000000..12785a9 --- /dev/null +++ b/Sources/ButtonCell.xib @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/ChildItemInfo.swift b/Sources/ChildItemInfo.swift index b6055ea..2bfa6ef 100644 --- a/Sources/ChildItemInfo.swift +++ b/Sources/ChildItemInfo.swift @@ -28,7 +28,7 @@ public struct ChildItemInfo { public var title: String public var image: UIImage? - // public var highlightedImage: UIImage? + public var highlightedImage: UIImage? public var color: UIColor? public init(title: String) { @@ -42,7 +42,7 @@ public struct ChildItemInfo { public init(title: String, image: UIImage?, highlightedImage: UIImage?) { self.init(title: title, image: image) - // self.highlightedImage = highlightedImage + self.highlightedImage = highlightedImage } public init(title: String, image: UIImage?, highlightedImage: UIImage?, color: UIColor?){ diff --git a/XLPagerTabStrip.xcodeproj/project.pbxproj b/XLPagerTabStrip.xcodeproj/project.pbxproj index 39dfd56..df739f4 100644 --- a/XLPagerTabStrip.xcodeproj/project.pbxproj +++ b/XLPagerTabStrip.xcodeproj/project.pbxproj @@ -14,6 +14,10 @@ 28E098C01C5003130083B788 /* SwipeDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28E098BF1C5003130083B788 /* SwipeDirection.swift */; }; 28F828811C494B2C00330CF4 /* XLPagerTabStrip.h in Headers */ = {isa = PBXBuildFile; fileRef = 28F828801C494B2C00330CF4 /* XLPagerTabStrip.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28F828881C494B2C00330CF4 /* XLPagerTabStrip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F8287D1C494B2C00330CF4 /* XLPagerTabStrip.framework */; }; + CB0986C41C51391600DF7087 /* ButtonBarPagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0986C31C51391600DF7087 /* ButtonBarPagerTabStripViewController.swift */; }; + CB0986C61C51395E00DF7087 /* ButtonBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0986C51C51395E00DF7087 /* ButtonBarView.swift */; }; + CB0986C81C5158A000DF7087 /* ButtonBarViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0986C71C5158A000DF7087 /* ButtonBarViewCell.swift */; }; + CB0986CA1C515D9A00DF7087 /* ButtonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB0986C91C515D9A00DF7087 /* ButtonCell.xib */; }; CB71C6EE1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB71C6ED1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift */; }; CB86ED6B1C4D6E6C00DA463B /* PagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB86ED6A1C4D6E6C00DA463B /* PagerTabStripViewController.swift */; }; CBA0A1FC1C502DA300C5748C /* BarPagerTabStripViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA0A1FA1C502DA300C5748C /* BarPagerTabStripViewController.swift */; }; @@ -42,6 +46,10 @@ 28F828871C494B2C00330CF4 /* XLPagerTabStripTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XLPagerTabStripTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28F8288E1C494B2C00330CF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Tests/Info.plist; sourceTree = ""; }; 28F8289B1C494BF100330CF4 /* Playground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground.playground; sourceTree = ""; }; + CB0986C31C51391600DF7087 /* ButtonBarPagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonBarPagerTabStripViewController.swift; sourceTree = ""; }; + CB0986C51C51395E00DF7087 /* ButtonBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonBarView.swift; sourceTree = ""; }; + CB0986C71C5158A000DF7087 /* ButtonBarViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonBarViewCell.swift; sourceTree = ""; }; + CB0986C91C515D9A00DF7087 /* ButtonCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ButtonCell.xib; sourceTree = ""; }; CB71C6ED1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedPagerTabStripViewController.swift; sourceTree = ""; }; CB86ED6A1C4D6E6C00DA463B /* PagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagerTabStripViewController.swift; sourceTree = ""; }; CBA0A1FA1C502DA300C5748C /* BarPagerTabStripViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarPagerTabStripViewController.swift; sourceTree = ""; }; @@ -70,7 +78,10 @@ 281BFDBE1C511B890090C26F /* Views */ = { isa = PBXGroup; children = ( + CB0986C91C515D9A00DF7087 /* ButtonCell.xib */, CBA0A1FB1C502DA300C5748C /* BarView.swift */, + CB0986C51C51395E00DF7087 /* ButtonBarView.swift */, + CB0986C71C5158A000DF7087 /* ButtonBarViewCell.swift */, ); name = Views; sourceTree = ""; @@ -81,6 +92,7 @@ CBA0A1FA1C502DA300C5748C /* BarPagerTabStripViewController.swift */, CB86ED6A1C4D6E6C00DA463B /* PagerTabStripViewController.swift */, CB71C6ED1C4EB988008EC806 /* SegmentedPagerTabStripViewController.swift */, + CB0986C31C51391600DF7087 /* ButtonBarPagerTabStripViewController.swift */, ); name = Controllers; sourceTree = ""; @@ -227,6 +239,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CB0986CA1C515D9A00DF7087 /* ButtonCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -244,6 +257,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CB0986C61C51395E00DF7087 /* ButtonBarView.swift in Sources */, CBA0A1FD1C502DA300C5748C /* BarView.swift in Sources */, 281BFDC31C511F120090C26F /* PagerTabStripOptions.swift in Sources */, CBA0A1FC1C502DA300C5748C /* BarPagerTabStripViewController.swift in Sources */, @@ -252,6 +266,8 @@ 281BFDC11C511C420090C26F /* ChildItemInfo.swift in Sources */, CB86ED6B1C4D6E6C00DA463B /* PagerTabStripViewController.swift in Sources */, 28E098C01C5003130083B788 /* SwipeDirection.swift in Sources */, + CB0986C81C5158A000DF7087 /* ButtonBarViewCell.swift in Sources */, + CB0986C41C51391600DF7087 /* ButtonBarPagerTabStripViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };