You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

234 lines
9.9 KiB

// TwitterPagerTabStripViewController.swift
// XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
//
// Copyright (c) 2017 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 struct TwitterPagerTabStripSettings {
public struct Style {
public var dotColor = UIColor(white: 1, alpha: 0.4)
public var selectedDotColor = UIColor.white
public var portraitTitleFont = UIFont.systemFont(ofSize: 18)
public var landscapeTitleFont = UIFont.systemFont(ofSize: 15)
public var titleColor = UIColor.white
}
public var style = Style()
}
open class TwitterPagerTabStripViewController: PagerTabStripViewController, PagerTabStripDataSource, PagerTabStripIsProgressiveDelegate {
open var settings = TwitterPagerTabStripSettings()
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
pagerBehaviour = .progressive(skipIntermediateViewControllers: true, elasticIndicatorLimit: true)
delegate = self
datasource = self
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
pagerBehaviour = .progressive(skipIntermediateViewControllers: true, elasticIndicatorLimit: true)
delegate = self
datasource = self
}
open override func viewDidLoad() {
super.viewDidLoad()
if titleView.superview == nil {
navigationItem.titleView = titleView
}
// keep watching the frame of titleView
titleView.addObserver(self, forKeyPath: "frame", options: [.new, .old], context: nil)
guard let navigationController = navigationController else {
fatalError("TwitterPagerTabStripViewController should be embedded in a UINavigationController")
}
titleView.frame = CGRect(x: 0, y: 0, width: navigationController.navigationBar.frame.width, height: navigationController.navigationBar.frame.height)
titleView.addSubview(titleScrollView)
titleView.addSubview(pageControl)
reloadNavigationViewItems()
}
open override func reloadPagerTabStripView() {
super.reloadPagerTabStripView()
guard isViewLoaded else { return }
reloadNavigationViewItems()
setNavigationViewItemsPosition(updateAlpha: true)
}
// MARK: - PagerTabStripDelegate
open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
// move indicator scroll view
guard let distance = distanceValue else { return }
var xOffset: CGFloat = 0
if fromIndex < toIndex {
xOffset = distance * CGFloat(fromIndex) + distance * progressPercentage
} else if fromIndex > toIndex {
xOffset = distance * CGFloat(fromIndex) - distance * progressPercentage
} else {
xOffset = distance * CGFloat(fromIndex)
}
titleScrollView.contentOffset = CGPoint(x: xOffset, y: 0)
// update alpha of titles
setAlphaWith(offset: xOffset, andDistance: distance)
// update page control page
pageControl.currentPage = currentIndex
}
open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int) {
fatalError()
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard object as AnyObject === titleView && keyPath == "frame" && change?[NSKeyValueChangeKey.kindKey] as? UInt == NSKeyValueChange.setting.rawValue else { return }
let oldRect = (change![NSKeyValueChangeKey.oldKey]! as AnyObject).cgRectValue
let newRect = (change![NSKeyValueChangeKey.oldKey]! as AnyObject).cgRectValue
if (oldRect?.equalTo(newRect!))! {
titleScrollView.frame = CGRect(x: 0, y: 0, width: titleView.frame.width, height: titleScrollView.frame.height)
setNavigationViewItemsPosition(updateAlpha: true)
}
}
deinit {
if isViewLoaded {
titleView.removeObserver(self, forKeyPath: "frame")
}
}
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setNavigationViewItemsPosition(updateAlpha: false)
}
// MARK: - Helpers
private lazy var titleView: UIView = {
let navigationView = UIView()
navigationView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return navigationView
}()
private lazy var titleScrollView: UIScrollView = { [unowned self] in
let titleScrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 44))
titleScrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
titleScrollView.bounces = true
titleScrollView.scrollsToTop = false
titleScrollView.delegate = self
titleScrollView.showsVerticalScrollIndicator = false
titleScrollView.showsHorizontalScrollIndicator = false
titleScrollView.isPagingEnabled = true
titleScrollView.isUserInteractionEnabled = false
titleScrollView.alwaysBounceHorizontal = true
titleScrollView.alwaysBounceVertical = false
return titleScrollView
}()
private lazy var pageControl: FXPageControl = { [unowned self] in
let pageControl = FXPageControl()
pageControl.backgroundColor = .clear
pageControl.dotSize = 3.8
pageControl.dotSpacing = 4.0
pageControl.dotColor = self.settings.style.dotColor
pageControl.selectedDotColor = self.settings.style.selectedDotColor
pageControl.isUserInteractionEnabled = false
return pageControl
}()
private var childTitleLabels = [UILabel]()
private func reloadNavigationViewItems() {
// remove all child view controller header labels
childTitleLabels.forEach { $0.removeFromSuperview() }
childTitleLabels.removeAll()
for (index, item) in viewControllers.enumerated() {
let child = item as! IndicatorInfoProvider // swiftlint:disable:this force_cast
let indicatorInfo = child.indicatorInfo(for: self)
let navTitleLabel: UILabel = {
let label = UILabel()
label.text = indicatorInfo.title
label.font = UIApplication.shared.statusBarOrientation.isPortrait ? settings.style.portraitTitleFont : settings.style.landscapeTitleFont
label.textColor = settings.style.titleColor
label.alpha = 0
return label
}()
navTitleLabel.alpha = currentIndex == index ? 1 : 0
navTitleLabel.textColor = settings.style.titleColor
titleScrollView.addSubview(navTitleLabel)
childTitleLabels.append(navTitleLabel)
}
}
private func setNavigationViewItemsPosition(updateAlpha: Bool) {
guard let distance = distanceValue else { return }
let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait
let navBarHeight: CGFloat = navigationController!.navigationBar.frame.size.height
for (index, label) in childTitleLabels.enumerated() {
if updateAlpha {
label.alpha = currentIndex == index ? 1 : 0
}
label.font = isPortrait ? settings.style.portraitTitleFont : settings.style.landscapeTitleFont
let viewSize = label.intrinsicContentSize
let originX = distance - viewSize.width/2 + CGFloat(index) * distance
let originY = (CGFloat(navBarHeight) - viewSize.height) / 2
label.frame = CGRect(x: originX, y: originY - 2, width: viewSize.width, height: viewSize.height)
label.tag = index
}
let xOffset = distance * CGFloat(currentIndex)
titleScrollView.contentOffset = CGPoint(x: xOffset, y: 0)
pageControl.numberOfPages = childTitleLabels.count
pageControl.currentPage = currentIndex
let viewSize = pageControl.sizeForNumber(ofPages: childTitleLabels.count)
let originX = distance - viewSize.width / 2
pageControl.frame = CGRect(x: originX, y: navBarHeight - 10, width: viewSize.width, height: viewSize.height)
}
private func setAlphaWith(offset: CGFloat, andDistance distance: CGFloat) {
for (index, label) in childTitleLabels.enumerated() {
label.alpha = {
if offset < distance * CGFloat(index) {
return (offset - distance * CGFloat(index - 1)) / distance
} else {
return 1 - ((offset - distance * CGFloat(index)) / distance)
}
}()
}
}
private var distanceValue: CGFloat? {
return navigationController.map { $0.navigationBar.convert($0.navigationBar.center, to: titleView) }?.x
}
}