ButtonBarView.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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. open var separatorView: UIView!
  16. internal let selectedBarHeight: CGFloat = 4
  17. internal let separatorHeight: CGFloat = 1.5
  18. var selectedIndex = 0
  19. @available(*, unavailable, message: "use init(frame:)")
  20. required public init?(coder aDecoder: NSCoder) {
  21. fatalError()
  22. }
  23. public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
  24. super.init(frame: frame, collectionViewLayout: layout)
  25. setup()
  26. NotificationCenter.default.addObserver(self, selector: #selector(updateTheme), name: .VLCThemeDidChangeNotification, object: nil)
  27. }
  28. func setup() {
  29. scrollsToTop = false
  30. showsHorizontalScrollIndicator = false
  31. separatorView = UIView(frame: CGRect(x: 0, y: self.frame.size.height - separatorHeight, width: self.frame.size.width, height: separatorHeight))
  32. addSubview(separatorView)
  33. selectedBar = UIView(frame: CGRect(x: 0, y: self.frame.size.height - CGFloat(self.selectedBarHeight), width: 0, height: CGFloat(self.selectedBarHeight)))
  34. addSubview(selectedBar)
  35. updateTheme()
  36. }
  37. @objc func updateTheme() {
  38. backgroundColor = PresentationTheme.current.colors.background
  39. selectedBar.backgroundColor = PresentationTheme.current.colors.orangeUI
  40. separatorView.backgroundColor = PresentationTheme.current.colors.mediaCategorySeparatorColor
  41. }
  42. open func moveTo(index: Int, animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
  43. selectedIndex = index
  44. updateSubviewPositions(animated, swipeDirection: swipeDirection, pagerScroll: pagerScroll)
  45. }
  46. open func move(fromIndex: Int, toIndex: Int, progressPercentage: CGFloat, pagerScroll: PagerScroll) {
  47. selectedIndex = progressPercentage > 0.5 ? toIndex : fromIndex
  48. let fromFrame = layoutAttributesForItem(at: IndexPath(item: fromIndex, section: 0))!.frame
  49. let numberOfItems = dataSource!.collectionView(self, numberOfItemsInSection: 0)
  50. var toFrame: CGRect
  51. if toIndex < 0 || toIndex > numberOfItems - 1 {
  52. if toIndex < 0 {
  53. let cellAtts = layoutAttributesForItem(at: IndexPath(item: 0, section: 0))
  54. toFrame = cellAtts!.frame.offsetBy(dx: -cellAtts!.frame.size.width, dy: 0)
  55. } else {
  56. let cellAtts = layoutAttributesForItem(at: IndexPath(item: (numberOfItems - 1), section: 0))
  57. toFrame = cellAtts!.frame.offsetBy(dx: cellAtts!.frame.size.width, dy: 0)
  58. }
  59. } else {
  60. toFrame = layoutAttributesForItem(at: IndexPath(item: toIndex, section: 0))!.frame
  61. }
  62. var targetFrame = fromFrame
  63. targetFrame.size.height = selectedBar.frame.size.height
  64. targetFrame.size.width += (toFrame.size.width - fromFrame.size.width) * progressPercentage
  65. targetFrame.origin.x += (toFrame.origin.x - fromFrame.origin.x) * progressPercentage
  66. selectedBar.frame = CGRect(x: targetFrame.origin.x, y: selectedBar.frame.origin.y, width: targetFrame.size.width, height: selectedBar.frame.size.height)
  67. var targetContentOffset: CGFloat = 0.0
  68. if contentSize.width > frame.size.width {
  69. let toContentOffset = contentOffsetForCell(withFrame: toFrame, andIndex: toIndex)
  70. let fromContentOffset = contentOffsetForCell(withFrame: fromFrame, andIndex: fromIndex)
  71. targetContentOffset = fromContentOffset + ((toContentOffset - fromContentOffset) * progressPercentage)
  72. }
  73. setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: false)
  74. }
  75. open func updateSubviewPositions(_ animated: Bool, swipeDirection: SwipeDirection, pagerScroll: PagerScroll) {
  76. var selectedBarFrame = selectedBar.frame
  77. let selectedCellIndexPath = IndexPath(item: selectedIndex, section: 0)
  78. let attributes = layoutAttributesForItem(at: selectedCellIndexPath)
  79. let selectedCellFrame = attributes!.frame
  80. updateContentOffset(animated: animated, pagerScroll: pagerScroll, toFrame: selectedCellFrame, toIndex: (selectedCellIndexPath as NSIndexPath).row)
  81. selectedBarFrame.size.width = selectedCellFrame.size.width
  82. selectedBarFrame.origin.x = selectedCellFrame.origin.x
  83. if animated {
  84. UIView.animate(withDuration: 0.3, animations: { [weak self] in
  85. if let strongSelf = self {
  86. strongSelf.selectedBar.frame = selectedBarFrame
  87. strongSelf.separatorView.frame = CGRect(x: 0, y: strongSelf.frame.size.height - strongSelf.separatorHeight, width: strongSelf.frame.size.width, height: strongSelf.separatorHeight)
  88. }
  89. })
  90. } else {
  91. selectedBar.frame = selectedBarFrame
  92. separatorView.frame = CGRect(x: 0, y: frame.size.height - separatorHeight, width: frame.size.width, height: separatorHeight)
  93. }
  94. }
  95. // MARK: - Helpers
  96. private func updateContentOffset(animated: Bool, pagerScroll: PagerScroll, toFrame: CGRect, toIndex: Int) {
  97. guard pagerScroll != .no || (pagerScroll != .scrollOnlyIfOutOfScreen && (toFrame.origin.x < contentOffset.x || toFrame.origin.x >= (contentOffset.x + frame.size.width - contentInset.left))) else { return }
  98. let targetContentOffset = contentSize.width > frame.size.width ? contentOffsetForCell(withFrame: toFrame, andIndex: toIndex) : 0
  99. setContentOffset(CGPoint(x: targetContentOffset, y: 0), animated: animated)
  100. }
  101. private func contentOffsetForCell(withFrame cellFrame: CGRect, andIndex index: Int) -> CGFloat {
  102. let alignmentOffset = (frame.size.width - cellFrame.size.width) * 0.5
  103. var contentOffset = cellFrame.origin.x - alignmentOffset
  104. contentOffset = max(0, contentOffset)
  105. contentOffset = min(contentSize.width - frame.size.width, contentOffset)
  106. return contentOffset
  107. }
  108. private func updateSelectedBarYPosition() {
  109. var selectedBarFrame = selectedBar.frame
  110. selectedBarFrame.origin.y = frame.size.height - selectedBarHeight
  111. selectedBarFrame.size.height = selectedBarHeight
  112. selectedBar.frame = selectedBarFrame
  113. }
  114. override open func layoutSubviews() {
  115. super.layoutSubviews()
  116. updateSelectedBarYPosition()
  117. }
  118. }