SAIToolboxInputView.swift 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. //
  2. // SAIToolboxInputView.swift
  3. // SAC
  4. //
  5. // Created by SAGESSE on 9/6/16.
  6. // Copyright © 2016-2017 SAGESSE. All rights reserved.
  7. //
  8. import UIKit
  9. // ## TODO
  10. // [x] SAIToolboxInputView - 数据源
  11. // [x] SAIToolboxInputView - 代理
  12. // [x] SAIToolboxInputView - 竖屏
  13. // [x] SAIToolboxInputView - 横屏
  14. // [x] SAIToolboxInputView - 自定义行/列数量
  15. // [x] SAIToolboxItemView - 选中高亮
  16. // [x] SAIToolboxItemView - 限制最大大小(80x80)
  17. // [x] SAIToolboxInputViewLayout - 快速滑动时性能问题
  18. @objc
  19. public protocol SAIToolboxInputViewDataSource: NSObjectProtocol {
  20. func numberOfToolboxItems(in toolbox: SAIToolboxInputView) -> Int
  21. func toolbox(_ toolbox: SAIToolboxInputView, toolboxItemForItemAt index: Int) -> SAIToolboxItem
  22. @objc optional func toolbox(_ toolbox: SAIToolboxInputView, numberOfRowsForSectionAt index: Int) -> Int
  23. @objc optional func toolbox(_ toolbox: SAIToolboxInputView, numberOfColumnsForSectionAt index: Int) -> Int
  24. }
  25. @objc
  26. public protocol SAIToolboxInputViewDelegate: NSObjectProtocol {
  27. @objc optional func inputViewContentSize(_ inputView: UIView) -> CGSize
  28. @objc optional func toolbox(_ toolbox: SAIToolboxInputView, insetForSectionAt index: Int) -> UIEdgeInsets
  29. @objc optional func toolbox(_ toolbox: SAIToolboxInputView, shouldSelectFor item: SAIToolboxItem) -> Bool
  30. @objc optional func toolbox(_ toolbox: SAIToolboxInputView, didSelectFor item: SAIToolboxItem)
  31. }
  32. open class SAIToolboxInputView: UIView {
  33. open func reloadData() {
  34. _contentView.reloadData()
  35. _updatePageControl()
  36. }
  37. open override func layoutSubviews() {
  38. super.layoutSubviews()
  39. if _cacheBounds?.width != bounds.width {
  40. _cacheBounds = bounds
  41. _updatePageControl()
  42. }
  43. }
  44. open override var intrinsicContentSize: CGSize {
  45. return delegate?.inputViewContentSize?(self) ?? CGSize(width: frame.width, height: 253)
  46. }
  47. open weak var delegate: SAIToolboxInputViewDelegate?
  48. open weak var dataSource: SAIToolboxInputViewDataSource?
  49. @objc func onPageChanged(_ sender: UIPageControl) {
  50. _contentView.setContentOffset(CGPoint(x: _contentView.bounds.width * CGFloat(sender.currentPage), y: 0), animated: true)
  51. }
  52. private func _updatePageControl() {
  53. let maxCount = _contentViewLayout.numberOfRows(in: 0) * _contentViewLayout.numberOfColumns(in: 0)
  54. let count = _contentView.numberOfItems(inSection: 0)
  55. let page = (count + (maxCount - 1)) / maxCount
  56. let currentPage = min(Int(_contentView.contentOffset.x / _contentView.frame.width), page - 1)
  57. _pageControl.numberOfPages = page
  58. _pageControl.currentPage = currentPage
  59. let x = CGFloat(currentPage) * _contentView.frame.width
  60. if _contentView.contentOffset.x != x {
  61. _contentView.contentOffset = CGPoint(x: x, y: 0)
  62. }
  63. }
  64. private func _init() {
  65. //_logger.trace()
  66. backgroundColor = .white
  67. _pageControl.numberOfPages = 0
  68. _pageControl.hidesForSinglePage = true
  69. _pageControl.pageIndicatorTintColor = UIColor.gray
  70. _pageControl.currentPageIndicatorTintColor = UIColor.darkGray
  71. _pageControl.translatesAutoresizingMaskIntoConstraints = false
  72. _pageControl.backgroundColor = .clear
  73. _pageControl.addTarget(self, action: #selector(onPageChanged(_:)), for: .valueChanged)
  74. _contentView.delegate = self
  75. _contentView.dataSource = self
  76. _contentView.scrollsToTop = false
  77. _contentView.isPagingEnabled = true
  78. _contentView.delaysContentTouches = false
  79. _contentView.showsVerticalScrollIndicator = false
  80. _contentView.showsHorizontalScrollIndicator = false
  81. _contentView.register(SAIToolboxItemView.self, forCellWithReuseIdentifier: "Item")
  82. _contentView.translatesAutoresizingMaskIntoConstraints = false
  83. _contentView.backgroundColor = .clear
  84. _line.translatesAutoresizingMaskIntoConstraints = false
  85. _line.layer.backgroundColor = UIColor(netHex: 0xE8E8E8).cgColor
  86. addSubview(_contentView)
  87. addSubview(_pageControl)
  88. addSubview(_line)
  89. addConstraint(_SAToolboxLayoutConstraintMake(_contentView, .top, .equal, self, .top))
  90. addConstraint(_SAToolboxLayoutConstraintMake(_contentView, .left, .equal, self, .left))
  91. addConstraint(_SAToolboxLayoutConstraintMake(_contentView, .right, .equal, self, .right))
  92. addConstraint(_SAToolboxLayoutConstraintMake(_contentView, .bottom, .equal, _pageControl, .top))
  93. addConstraint(_SAToolboxLayoutConstraintMake(_pageControl, .left, .equal, self, .left))
  94. addConstraint(_SAToolboxLayoutConstraintMake(_pageControl, .right, .equal, self, .right))
  95. addConstraint(_SAToolboxLayoutConstraintMake(_pageControl, .bottom, .equal, self, .bottom))
  96. addConstraint(_SAToolboxLayoutConstraintMake(_pageControl, .height, .equal, nil, .notAnAttribute, 32))
  97. addConstraint(_SAToolboxLayoutConstraintMake(_line, .top, .equal, self, .top))
  98. addConstraint(_SAToolboxLayoutConstraintMake(_line, .left, .equal, self, .left))
  99. addConstraint(_SAToolboxLayoutConstraintMake(_line, .right, .equal, self, .right))
  100. addConstraint(_SAToolboxLayoutConstraintMake(_line, .height, .equal, nil, .notAnAttribute, 0.5))
  101. }
  102. private var _cacheBounds: CGRect?
  103. fileprivate lazy var _pageControl: UIPageControl = UIPageControl()
  104. fileprivate lazy var _contentViewLayout: SAIToolboxInputViewLayout = SAIToolboxInputViewLayout()
  105. fileprivate lazy var _contentView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: self._contentViewLayout)
  106. private lazy var _line: UILabel = UILabel()
  107. public override init(frame: CGRect) {
  108. super.init(frame: frame)
  109. _init()
  110. }
  111. public required init?(coder aDecoder: NSCoder) {
  112. super.init(coder: aDecoder)
  113. _init()
  114. }
  115. }
  116. // MARK: - UICollectionViewDataSource & SAIToolboxInputViewLayoutDelegate
  117. extension SAIToolboxInputView: UICollectionViewDataSource, SAIToolboxInputViewLayoutDelegate {
  118. public func scrollViewDidScroll(_ scrollView: UIScrollView) {
  119. _pageControl.currentPage = Int(round(scrollView.contentOffset.x / scrollView.frame.width))
  120. }
  121. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  122. return dataSource?.numberOfToolboxItems(in: self) ?? 0
  123. }
  124. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  125. return collectionView.dequeueReusableCell(withReuseIdentifier: "Item", for: indexPath)
  126. }
  127. public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  128. guard let cell = cell as? SAIToolboxItemView else {
  129. return
  130. }
  131. cell.item = dataSource?.toolbox(self, toolboxItemForItemAt: indexPath.item)
  132. cell.handler = self
  133. }
  134. internal func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: SAIToolboxInputViewLayout, insetForSectionAt index: Int) -> UIEdgeInsets {
  135. return delegate?.toolbox?(self, insetForSectionAt: index) ?? UIEdgeInsets(top: 12, left: 10, bottom: 12, right: 10)
  136. }
  137. internal func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: SAIToolboxInputViewLayout, numberOfRowsForSectionAt index: Int) -> Int {
  138. return dataSource?.toolbox?(self, numberOfRowsForSectionAt: index) ?? 2
  139. }
  140. internal func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: SAIToolboxInputViewLayout, numberOfColumnsForSectionAt index: Int) -> Int {
  141. return dataSource?.toolbox?(self, numberOfColumnsForSectionAt: index) ?? 4
  142. }
  143. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  144. collectionView.deselectItem(at: indexPath, animated: true)
  145. guard let item = dataSource?.toolbox(self, toolboxItemForItemAt: indexPath.row) else {
  146. return
  147. }
  148. if delegate?.toolbox?(self, shouldSelectFor: item) ?? true {
  149. delegate?.toolbox?(self, didSelectFor: item)
  150. }
  151. }
  152. }
  153. @inline(__always)
  154. internal func _SAToolboxLayoutConstraintMake(_ item: AnyObject, _ attr1: NSLayoutConstraint.Attribute, _ related: NSLayoutConstraint.Relation, _ toItem: AnyObject? = nil, _ attr2: NSLayoutConstraint.Attribute = .notAnAttribute, _ constant: CGFloat = 0, priority: UILayoutPriority = UILayoutPriority.init(1000.0), multiplier: CGFloat = 1, output: UnsafeMutablePointer<NSLayoutConstraint?>? = nil) -> NSLayoutConstraint {
  155. let c = NSLayoutConstraint(item:item, attribute:attr1, relatedBy:related, toItem:toItem, attribute:attr2, multiplier:multiplier, constant:constant)
  156. c.priority = priority
  157. if output != nil {
  158. output?.pointee = c
  159. }
  160. return c
  161. }