ButtonBarView.swift 6.8 KB

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