ButtonBarView.swift 5.8 KB

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