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.

191 lines
8.0 KiB

6 years ago
  1. // ButtonBarView.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 UIKit
  25. public enum PagerScroll {
  26. case no
  27. case yes
  28. case scrollOnlyIfOutOfScreen
  29. }
  30. public enum SelectedBarAlignment {
  31. case left
  32. case center
  33. case right
  34. case progressive
  35. }
  36. public enum SelectedBarVerticalAlignment {
  37. case top
  38. case middle
  39. case bottom
  40. }
  41. open class ButtonBarView: UICollectionView {
  42. open lazy var selectedBar: UIView = { [unowned self] in
  43. let bar = UIView(frame: CGRect(x: 0, y: self.frame.size.height - CGFloat(self.selectedBarHeight), width: 0, height: CGFloat(self.selectedBarHeight)))
  44. bar.layer.zPosition = 9999
  45. return bar
  46. }()
  47. internal var selectedBarHeight: CGFloat = 4 {
  48. didSet {
  49. updateSelectedBarYPosition()
  50. }
  51. }
  52. var selectedBarVerticalAlignment: SelectedBarVerticalAlignment = .bottom
  53. var selectedBarAlignment: SelectedBarAlignment = .center
  54. var selectedIndex = 0
  55. required public init?(coder aDecoder: NSCoder) {
  56. super.init(coder: aDecoder)
  57. addSubview(selectedBar)
  58. }
  59. public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
  60. super.init(frame: frame, collectionViewLayout: layout)
  61. addSubview(selectedBar)
  62. }
  63. open func moveTo(index: Int, animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
  64. selectedIndex = index
  65. updateSelectedBarPosition(animated, swipeDirection: swipeDirection, pagerScroll: pagerScroll)
  66. }
  67. open func move(fromIndex: Int, toIndex: Int, progressPercentage: CGFloat, pagerScroll: PagerScroll) {
  68. selectedIndex = progressPercentage > 0.5 ? toIndex : fromIndex
  69. let fromFrame = layoutAttributesForItem(at: IndexPath(item: fromIndex, section: 0))!.frame
  70. let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
  71. var toFrame: CGRect
  72. if toIndex < 0 || toIndex > numberOfItems - 1 {
  73. if toIndex < 0 {
  74. let cellAtts = layoutAttributesForItem(at: IndexPath(item: 0, section: 0))
  75. toFrame = cellAtts!.frame.offsetBy(dx: -cellAtts!.frame.size.width, dy: 0)
  76. } else {
  77. let cellAtts = layoutAttributesForItem(at: IndexPath(item: (numberOfItems - 1), section: 0))
  78. toFrame = cellAtts!.frame.offsetBy(dx: cellAtts!.frame.size.width, dy: 0)
  79. }
  80. } else {
  81. toFrame = layoutAttributesForItem(at: IndexPath(item: toIndex, section: 0))!.frame
  82. }
  83. var targetFrame = fromFrame
  84. targetFrame.size.height = selectedBar.frame.size.height
  85. targetFrame.size.width += (toFrame.size.width - fromFrame.size.width) * progressPercentage
  86. targetFrame.origin.x += (toFrame.origin.x - fromFrame.origin.x) * progressPercentage
  87. selectedBar.frame = CGRect(x: targetFrame.origin.x, y: selectedBar.frame.origin.y, width: targetFrame.size.width, height: selectedBar.frame.size.height)
  88. var targetContentOffset: CGFloat = 0.0
  89. if contentSize.width > frame.size.width {
  90. let toContentOffset = contentOffsetForCell(withFrame: toFrame, andIndex: toIndex)
  91. let fromContentOffset = contentOffsetForCell(withFrame: fromFrame, andIndex: fromIndex)
  92. targetContentOffset = fromContentOffset + ((toContentOffset - fromContentOffset) * progressPercentage)
  93. }
  94. setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: false)
  95. }
  96. open func updateSelectedBarPosition(_ animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
  97. var selectedBarFrame = selectedBar.frame
  98. let selectedCellIndexPath = IndexPath(item: selectedIndex, section: 0)
  99. let attributes = layoutAttributesForItem(at: selectedCellIndexPath)
  100. let selectedCellFrame = attributes!.frame
  101. updateContentOffset(animated: animated, pagerScroll: pagerScroll, toFrame: selectedCellFrame, toIndex: (selectedCellIndexPath as NSIndexPath).row)
  102. selectedBarFrame.size.width = selectedCellFrame.size.width
  103. selectedBarFrame.origin.x = selectedCellFrame.origin.x
  104. if animated {
  105. UIView.animate(withDuration: 0.3, animations: { [weak self] in
  106. self?.selectedBar.frame = selectedBarFrame
  107. })
  108. } else {
  109. selectedBar.frame = selectedBarFrame
  110. }
  111. }
  112. // MARK: - Helpers
  113. private func updateContentOffset(animated: Bool, pagerScroll: PagerScroll, toFrame: CGRect, toIndex: Int) {
  114. guard pagerScroll != .no || (pagerScroll != .scrollOnlyIfOutOfScreen && (toFrame.origin.x < contentOffset.x || toFrame.origin.x >= (contentOffset.x + frame.size.width - contentInset.left))) else { return }
  115. let targetContentOffset = contentSize.width > frame.size.width ? contentOffsetForCell(withFrame: toFrame, andIndex: toIndex) : 0
  116. setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: animated)
  117. }
  118. private func contentOffsetForCell(withFrame cellFrame: CGRect, andIndex index: Int) -> CGFloat {
  119. let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset // swiftlint:disable:this force_cast
  120. var alignmentOffset: CGFloat = 0.0
  121. switch selectedBarAlignment {
  122. case .left:
  123. alignmentOffset = sectionInset.left
  124. case .right:
  125. alignmentOffset = frame.size.width - sectionInset.right - cellFrame.size.width
  126. case .center:
  127. alignmentOffset = (frame.size.width - cellFrame.size.width) * 0.5
  128. case .progressive:
  129. let cellHalfWidth = cellFrame.size.width * 0.5
  130. let leftAlignmentOffset = sectionInset.left + cellHalfWidth
  131. let rightAlignmentOffset = frame.size.width - sectionInset.right - cellHalfWidth
  132. let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
  133. let progress = index / (numberOfItems - 1)
  134. alignmentOffset = leftAlignmentOffset + (rightAlignmentOffset - leftAlignmentOffset) * CGFloat(progress) - cellHalfWidth
  135. }
  136. var contentOffset = cellFrame.origin.x - alignmentOffset
  137. contentOffset = max(0, contentOffset)
  138. contentOffset = min(contentSize.width - frame.size.width, contentOffset)
  139. return contentOffset
  140. }
  141. private func updateSelectedBarYPosition() {
  142. var selectedBarFrame = selectedBar.frame
  143. switch selectedBarVerticalAlignment {
  144. case .top:
  145. selectedBarFrame.origin.y = 0
  146. case .middle:
  147. selectedBarFrame.origin.y = (frame.size.height - selectedBarHeight) / 2
  148. case .bottom:
  149. selectedBarFrame.origin.y = frame.size.height - selectedBarHeight
  150. }
  151. selectedBarFrame.size.height = selectedBarHeight
  152. selectedBar.frame = selectedBarFrame
  153. }
  154. override open func layoutSubviews() {
  155. super.layoutSubviews()
  156. updateSelectedBarYPosition()
  157. }
  158. }