ButtonBarView.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. //
  2. // ButtonBarView.swift
  3. // VLC-iOS
  4. //
  5. // Created by Carola Nitz on 6/1/18.
  6. // Copyright © 2018 VideoLAN. All rights reserved.
  7. //
  8. import Foundation
  9. open class ButtonBarView: UICollectionView {
  10. open lazy var selectedBar: UIView = { [unowned self] in
  11. let bar = UIView(frame: CGRect(x: 0, y: self.frame.size.height - CGFloat(self.selectedBarHeight), width: 0, height: CGFloat(self.selectedBarHeight)))
  12. bar.layer.zPosition = 9999
  13. return bar
  14. }()
  15. internal var selectedBarHeight: CGFloat = 4 {
  16. didSet {
  17. updateSelectedBarYPosition()
  18. }
  19. }
  20. var selectedIndex = 0
  21. required public init?(coder aDecoder: NSCoder) {
  22. super.init(coder: aDecoder)
  23. addSubview(selectedBar)
  24. }
  25. public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
  26. super.init(frame: frame, collectionViewLayout: layout)
  27. addSubview(selectedBar)
  28. }
  29. open func moveTo(index: Int, animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
  30. selectedIndex = index
  31. updateSelectedBarPosition(animated, swipeDirection: swipeDirection, pagerScroll: pagerScroll)
  32. }
  33. open func move(fromIndex: Int, toIndex: Int, progressPercentage: CGFloat, pagerScroll: PagerScroll) {
  34. selectedIndex = progressPercentage > 0.5 ? toIndex : fromIndex
  35. let fromFrame = layoutAttributesForItem(at: IndexPath(item: fromIndex, section: 0))!.frame
  36. let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
  37. var toFrame: CGRect
  38. if toIndex < 0 || toIndex > numberOfItems - 1 {
  39. if toIndex < 0 {
  40. let cellAtts = layoutAttributesForItem(at: IndexPath(item: 0, section: 0))
  41. toFrame = cellAtts!.frame.offsetBy(dx: -cellAtts!.frame.size.width, dy: 0)
  42. } else {
  43. let cellAtts = layoutAttributesForItem(at: IndexPath(item: (numberOfItems - 1), section: 0))
  44. toFrame = cellAtts!.frame.offsetBy(dx: cellAtts!.frame.size.width, dy: 0)
  45. }
  46. } else {
  47. toFrame = layoutAttributesForItem(at: IndexPath(item: toIndex, section: 0))!.frame
  48. }
  49. var targetFrame = fromFrame
  50. targetFrame.size.height = selectedBar.frame.size.height
  51. targetFrame.size.width += (toFrame.size.width - fromFrame.size.width) * progressPercentage
  52. targetFrame.origin.x += (toFrame.origin.x - fromFrame.origin.x) * progressPercentage
  53. selectedBar.frame = CGRect(x: targetFrame.origin.x, y: selectedBar.frame.origin.y, width: targetFrame.size.width, height: selectedBar.frame.size.height)
  54. var targetContentOffset: CGFloat = 0.0
  55. if contentSize.width > frame.size.width {
  56. let toContentOffset = contentOffsetForCell(withFrame: toFrame, andIndex: toIndex)
  57. let fromContentOffset = contentOffsetForCell(withFrame: fromFrame, andIndex: fromIndex)
  58. targetContentOffset = fromContentOffset + ((toContentOffset - fromContentOffset) * progressPercentage)
  59. }
  60. setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: false)
  61. }
  62. open func updateSelectedBarPosition(_ animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
  63. var selectedBarFrame = selectedBar.frame
  64. let selectedCellIndexPath = IndexPath(item: selectedIndex, section: 0)
  65. let attributes = layoutAttributesForItem(at: selectedCellIndexPath)
  66. let selectedCellFrame = attributes!.frame
  67. updateContentOffset(animated: animated, pagerScroll: pagerScroll, toFrame: selectedCellFrame, toIndex: (selectedCellIndexPath as NSIndexPath).row)
  68. selectedBarFrame.size.width = selectedCellFrame.size.width
  69. selectedBarFrame.origin.x = selectedCellFrame.origin.x
  70. if animated {
  71. UIView.animate(withDuration: 0.3, animations: { [weak self] in
  72. self?.selectedBar.frame = selectedBarFrame
  73. })
  74. } else {
  75. selectedBar.frame = selectedBarFrame
  76. }
  77. }
  78. // MARK: - Helpers
  79. private func updateContentOffset(animated: Bool, pagerScroll: PagerScroll, toFrame: CGRect, toIndex: Int) {
  80. guard pagerScroll != .no || (pagerScroll != .scrollOnlyIfOutOfScreen && (toFrame.origin.x < contentOffset.x || toFrame.origin.x >= (contentOffset.x + frame.size.width - contentInset.left))) else { return }
  81. let targetContentOffset = contentSize.width > frame.size.width ? contentOffsetForCell(withFrame: toFrame, andIndex: toIndex) : 0
  82. setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: animated)
  83. }
  84. private func contentOffsetForCell(withFrame cellFrame: CGRect, andIndex index: Int) -> CGFloat {
  85. let alignmentOffset = (frame.size.width - cellFrame.size.width) * 0.5
  86. var contentOffset = cellFrame.origin.x - alignmentOffset
  87. contentOffset = max(0, contentOffset)
  88. contentOffset = min(contentSize.width - frame.size.width, contentOffset)
  89. return contentOffset
  90. }
  91. private func updateSelectedBarYPosition() {
  92. var selectedBarFrame = selectedBar.frame
  93. selectedBarFrame.origin.y = frame.size.height - selectedBarHeight
  94. selectedBarFrame.size.height = selectedBarHeight
  95. selectedBar.frame = selectedBarFrame
  96. }
  97. override open func layoutSubviews() {
  98. super.layoutSubviews()
  99. updateSelectedBarYPosition()
  100. }
  101. }