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

6 years ago
  1. // TwitterPagerTabStripViewController.swift
  2. // XLPagerTabStrip ( https://github.com/xmartlabs/XLPagerTabStrip )
  3. //
  4. // Copyright (c) 2017 Xmartlabs ( http://xmartlabs.com )
  5. //
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. import Foundation
  25. public struct TwitterPagerTabStripSettings {
  26. public struct Style {
  27. public var dotColor = UIColor(white: 1, alpha: 0.4)
  28. public var selectedDotColor = UIColor.white
  29. public var portraitTitleFont = UIFont.systemFont(ofSize: 18)
  30. public var landscapeTitleFont = UIFont.systemFont(ofSize: 15)
  31. public var titleColor = UIColor.white
  32. }
  33. public var style = Style()
  34. }
  35. open class TwitterPagerTabStripViewController: PagerTabStripViewController, PagerTabStripDataSource, PagerTabStripIsProgressiveDelegate {
  36. open var settings = TwitterPagerTabStripSettings()
  37. public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
  38. super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
  39. pagerBehaviour = .progressive(skipIntermediateViewControllers: true, elasticIndicatorLimit: true)
  40. delegate = self
  41. datasource = self
  42. }
  43. required public init?(coder aDecoder: NSCoder) {
  44. super.init(coder: aDecoder)
  45. pagerBehaviour = .progressive(skipIntermediateViewControllers: true, elasticIndicatorLimit: true)
  46. delegate = self
  47. datasource = self
  48. }
  49. open override func viewDidLoad() {
  50. super.viewDidLoad()
  51. if titleView.superview == nil {
  52. navigationItem.titleView = titleView
  53. }
  54. // keep watching the frame of titleView
  55. titleView.addObserver(self, forKeyPath: "frame", options: [.new, .old], context: nil)
  56. guard let navigationController = navigationController else {
  57. fatalError("TwitterPagerTabStripViewController should be embedded in a UINavigationController")
  58. }
  59. titleView.frame = CGRect(x: 0, y: 0, width: navigationController.navigationBar.frame.width, height: navigationController.navigationBar.frame.height)
  60. titleView.addSubview(titleScrollView)
  61. titleView.addSubview(pageControl)
  62. reloadNavigationViewItems()
  63. }
  64. open override func reloadPagerTabStripView() {
  65. super.reloadPagerTabStripView()
  66. guard isViewLoaded else { return }
  67. reloadNavigationViewItems()
  68. setNavigationViewItemsPosition(updateAlpha: true)
  69. }
  70. // MARK: - PagerTabStripDelegate
  71. open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int, withProgressPercentage progressPercentage: CGFloat, indexWasChanged: Bool) {
  72. // move indicator scroll view
  73. guard let distance = distanceValue else { return }
  74. var xOffset: CGFloat = 0
  75. if fromIndex < toIndex {
  76. xOffset = distance * CGFloat(fromIndex) + distance * progressPercentage
  77. } else if fromIndex > toIndex {
  78. xOffset = distance * CGFloat(fromIndex) - distance * progressPercentage
  79. } else {
  80. xOffset = distance * CGFloat(fromIndex)
  81. }
  82. titleScrollView.contentOffset = CGPoint(x: xOffset, y: 0)
  83. // update alpha of titles
  84. setAlphaWith(offset: xOffset, andDistance: distance)
  85. // update page control page
  86. pageControl.currentPage = currentIndex
  87. }
  88. open func updateIndicator(for viewController: PagerTabStripViewController, fromIndex: Int, toIndex: Int) {
  89. fatalError()
  90. }
  91. open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  92. guard object as AnyObject === titleView && keyPath == "frame" && change?[NSKeyValueChangeKey.kindKey] as? UInt == NSKeyValueChange.setting.rawValue else { return }
  93. let oldRect = (change![NSKeyValueChangeKey.oldKey]! as AnyObject).cgRectValue
  94. let newRect = (change![NSKeyValueChangeKey.oldKey]! as AnyObject).cgRectValue
  95. if (oldRect?.equalTo(newRect!))! {
  96. titleScrollView.frame = CGRect(x: 0, y: 0, width: titleView.frame.width, height: titleScrollView.frame.height)
  97. setNavigationViewItemsPosition(updateAlpha: true)
  98. }
  99. }
  100. deinit {
  101. if isViewLoaded {
  102. titleView.removeObserver(self, forKeyPath: "frame")
  103. }
  104. }
  105. open override func viewDidLayoutSubviews() {
  106. super.viewDidLayoutSubviews()
  107. setNavigationViewItemsPosition(updateAlpha: false)
  108. }
  109. // MARK: - Helpers
  110. private lazy var titleView: UIView = {
  111. let navigationView = UIView()
  112. navigationView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  113. return navigationView
  114. }()
  115. private lazy var titleScrollView: UIScrollView = { [unowned self] in
  116. let titleScrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 44))
  117. titleScrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  118. titleScrollView.bounces = true
  119. titleScrollView.scrollsToTop = false
  120. titleScrollView.delegate = self
  121. titleScrollView.showsVerticalScrollIndicator = false
  122. titleScrollView.showsHorizontalScrollIndicator = false
  123. titleScrollView.isPagingEnabled = true
  124. titleScrollView.isUserInteractionEnabled = false
  125. titleScrollView.alwaysBounceHorizontal = true
  126. titleScrollView.alwaysBounceVertical = false
  127. return titleScrollView
  128. }()
  129. private lazy var pageControl: FXPageControl = { [unowned self] in
  130. let pageControl = FXPageControl()
  131. pageControl.backgroundColor = .clear
  132. pageControl.dotSize = 3.8
  133. pageControl.dotSpacing = 4.0
  134. pageControl.dotColor = self.settings.style.dotColor
  135. pageControl.selectedDotColor = self.settings.style.selectedDotColor
  136. pageControl.isUserInteractionEnabled = false
  137. return pageControl
  138. }()
  139. private var childTitleLabels = [UILabel]()
  140. private func reloadNavigationViewItems() {
  141. // remove all child view controller header labels
  142. childTitleLabels.forEach { $0.removeFromSuperview() }
  143. childTitleLabels.removeAll()
  144. for (index, item) in viewControllers.enumerated() {
  145. let child = item as! IndicatorInfoProvider // swiftlint:disable:this force_cast
  146. let indicatorInfo = child.indicatorInfo(for: self)
  147. let navTitleLabel: UILabel = {
  148. let label = UILabel()
  149. label.text = indicatorInfo.title
  150. label.font = UIApplication.shared.statusBarOrientation.isPortrait ? settings.style.portraitTitleFont : settings.style.landscapeTitleFont
  151. label.textColor = settings.style.titleColor
  152. label.alpha = 0
  153. return label
  154. }()
  155. navTitleLabel.alpha = currentIndex == index ? 1 : 0
  156. navTitleLabel.textColor = settings.style.titleColor
  157. titleScrollView.addSubview(navTitleLabel)
  158. childTitleLabels.append(navTitleLabel)
  159. }
  160. }
  161. private func setNavigationViewItemsPosition(updateAlpha: Bool) {
  162. guard let distance = distanceValue else { return }
  163. let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait
  164. let navBarHeight: CGFloat = navigationController!.navigationBar.frame.size.height
  165. for (index, label) in childTitleLabels.enumerated() {
  166. if updateAlpha {
  167. label.alpha = currentIndex == index ? 1 : 0
  168. }
  169. label.font = isPortrait ? settings.style.portraitTitleFont : settings.style.landscapeTitleFont
  170. let viewSize = label.intrinsicContentSize
  171. let originX = distance - viewSize.width/2 + CGFloat(index) * distance
  172. let originY = (CGFloat(navBarHeight) - viewSize.height) / 2
  173. label.frame = CGRect(x: originX, y: originY - 2, width: viewSize.width, height: viewSize.height)
  174. label.tag = index
  175. }
  176. let xOffset = distance * CGFloat(currentIndex)
  177. titleScrollView.contentOffset = CGPoint(x: xOffset, y: 0)
  178. pageControl.numberOfPages = childTitleLabels.count
  179. pageControl.currentPage = currentIndex
  180. let viewSize = pageControl.sizeForNumber(ofPages: childTitleLabels.count)
  181. let originX = distance - viewSize.width / 2
  182. pageControl.frame = CGRect(x: originX, y: navBarHeight - 10, width: viewSize.width, height: viewSize.height)
  183. }
  184. private func setAlphaWith(offset: CGFloat, andDistance distance: CGFloat) {
  185. for (index, label) in childTitleLabels.enumerated() {
  186. label.alpha = {
  187. if offset < distance * CGFloat(index) {
  188. return (offset - distance * CGFloat(index - 1)) / distance
  189. } else {
  190. return 1 - ((offset - distance * CGFloat(index)) / distance)
  191. }
  192. }()
  193. }
  194. }
  195. private var distanceValue: CGFloat? {
  196. return navigationController.map { $0.navigationBar.convert($0.navigationBar.center, to: titleView) }?.x
  197. }
  198. }