AKExcelView.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. //
  2. // ExcelViewSwifty.swift
  3. // YunYingSwift
  4. //
  5. // Created by AlasKu on 17/2/10.
  6. // Copyright © 2017年 innostic. All rights reserved.
  7. //
  8. import UIKit
  9. import NVActivityIndicatorView
  10. let AKCollectionViewCellIdentifier = "AKCollectionView_Cell"
  11. let AKCollectionViewHeaderIdentifier = "AKCollectionView_Header"
  12. @objc protocol AKExcelViewDelegate : NSObjectProtocol {
  13. @objc optional func excelView(_ excelView: AKExcelView, didSelectItemAt indexPath: IndexPath)
  14. @objc optional func excelView(_ excelView: AKExcelView, viewAt indexPath: IndexPath) -> UIView?
  15. @objc optional func excelView(_ excelView: AKExcelView, attributedStringAt indexPath: IndexPath) -> NSAttributedString?
  16. }
  17. class AKExcelView: UIView , UICollectionViewDelegate , UICollectionViewDataSource , UIScrollViewDelegate , UICollectionViewDelegateFlowLayout{
  18. /// Delegate
  19. weak var delegate : AKExcelViewDelegate?
  20. /// CellTextMargin
  21. var textMargin : CGFloat = 5
  22. /// Cell Max width
  23. var itemMaxWidth : CGFloat = 200
  24. /// cell Min width
  25. var itemMinWidth : CGFloat = 50
  26. /// cell heihth
  27. var itemHeight : CGFloat = 44
  28. /// header Height
  29. var headerHeight : CGFloat = 44
  30. /// header BackgroundColor
  31. var headerBackgroundColor : UIColor = UIColor.lightGray
  32. /// header Text Color
  33. var headerTextColor : UIColor = UIColor.black
  34. /// header Text Font
  35. var headerTextFontSize : UIFont = UIFont.systemFont(ofSize: 15)
  36. /// contenTCell TextColor
  37. var contentTextColor : UIColor = UIColor.black
  38. /// content backgroung Color
  39. var contentBackgroundColor : UIColor = UIColor.white
  40. /// content Text Font
  41. var contentTextFontSize : UIFont = UIFont.systemFont(ofSize: 13)
  42. /// letf freeze column
  43. var leftFreezeColumn : Int = 1
  44. /// header Titles
  45. var headerTitles : [String]?
  46. /// content Data
  47. var contentData : Array<NSObject>?
  48. /// 颜色支持
  49. var contentDataColor: [[UIColor]]?
  50. /// set Column widths
  51. var columnWidthSetting : Dictionary<Int, CGFloat>?
  52. /// CelledgeInset
  53. var itemInsets : UIEdgeInsets?
  54. /// showsProperties
  55. var properties : Array<String>?
  56. /// autoScrollItem default is false
  57. var autoScrollToNearItem = false
  58. /// if set, there is no loading view when reload datas. default is true
  59. var isNeedLoadingView = true
  60. fileprivate lazy var horizontalShadow : CAShapeLayer = { //水平阴影
  61. let s = CAShapeLayer()
  62. s.strokeColor = UIColor.lightGray.cgColor
  63. s.shadowColor = UIColor.black.cgColor
  64. s.shadowOffset = CGSize.init(width: 2, height: 0)
  65. s.shadowOpacity = 1
  66. return s
  67. }()
  68. fileprivate lazy var veritcalShadow : CAShapeLayer = { //垂直阴影
  69. let s = CAShapeLayer()
  70. s.strokeColor = UIColor.lightGray.cgColor
  71. s.shadowColor = UIColor.black.cgColor
  72. s.shadowOffset = CGSize.init(width: 0, height: 3)
  73. s.shadowOpacity = 1
  74. return s
  75. }()
  76. // MARK: - Public Method
  77. override init(frame: CGRect) {
  78. super.init(frame: frame)
  79. setup()
  80. }
  81. required init?(coder aDecoder: NSCoder) {
  82. super.init(coder: aDecoder)
  83. setup()
  84. }
  85. override func layoutSubviews() {
  86. super.layoutSubviews()
  87. setUpFrames()
  88. }
  89. func reloadData() {
  90. if isNeedLoadingView {
  91. // 开启Loading
  92. NVActivityIndicatorPresenter.sharedInstance.startAnimating(ActivityData(message: "数据加载中...", color: UIColor.loding()), NVActivityIndicatorView.DEFAULT_FADE_IN_ANIMATION)
  93. }
  94. DispatchQueue.global().sync {
  95. self.dataManager.caculateData()
  96. DispatchQueue.main.async {
  97. if self.isNeedLoadingView {
  98. NVActivityIndicatorPresenter.sharedInstance.stopAnimating(NVActivityIndicatorView.DEFAULT_FADE_OUT_ANIMATION)
  99. }
  100. self.setUpFrames()
  101. self.headFreezeCollectionView.reloadData()
  102. self.headMovebleCollectionView.reloadData()
  103. self.contentFreezeCollectionView.reloadData()
  104. self.contentMoveableCollectionView.reloadData()
  105. }
  106. }
  107. }
  108. func sizeForItem(item: Int) -> CGSize {
  109. if item < leftFreezeColumn {
  110. return NSCoder.cgSize(for: self.dataManager.freezeItemSize[item])
  111. } else {
  112. return NSCoder.cgSize(for: self.dataManager.slideItemSize[item - leftFreezeColumn])
  113. }
  114. }
  115. //MARK: - Private Method
  116. private func setup() {
  117. dataManager.excelView = self
  118. clipsToBounds = true
  119. addSubview(headFreezeCollectionView)
  120. addSubview(contentFreezeCollectionView)
  121. addSubview(contentScrollView)
  122. contentScrollView.addSubview(headMovebleCollectionView)
  123. contentScrollView.addSubview(contentMoveableCollectionView)
  124. contentMoveableCollectionView.contentInset = UIEdgeInsets(top: -1, left: 0, bottom: 0, right: 0)
  125. contentFreezeCollectionView.contentInset = UIEdgeInsets(top: -1, left: 0, bottom: 0, right: 0)
  126. layer.addSublayer(horizontalShadow)
  127. contentScrollView.layer.addSublayer(veritcalShadow)
  128. }
  129. fileprivate func showHorizontalDivideShadowLayer() {
  130. if horizontalShadow.path == nil {
  131. let path = UIBezierPath()
  132. path.move(to: CGPoint(x: 0, y: headerHeight))
  133. path.addLine(to: CGPoint(x: min(self.bounds.width, self.dataManager.freezeWidth + self.dataManager.slideWidth), y: headerHeight))
  134. path.lineWidth = 0.5
  135. horizontalShadow.path = path.cgPath
  136. }
  137. }
  138. fileprivate func dismissHorizontalDivideShadowLayer() {
  139. horizontalShadow.path = nil
  140. }
  141. fileprivate func showVerticalDivideShadowLayer() {
  142. if veritcalShadow.path == nil {
  143. let path = UIBezierPath()
  144. let heigh = contentScrollView.contentSize.height // + headFreezeCollectionView.contentSize.height
  145. path.move(to: CGPoint(x: 0, y: 0))
  146. path.addLine(to: CGPoint(x: 0, y: heigh))
  147. path.lineWidth = 0.5
  148. veritcalShadow.path = path.cgPath
  149. }
  150. }
  151. fileprivate func dismissVerticalDivideShadowLayer() {
  152. veritcalShadow.path = nil
  153. }
  154. fileprivate func setUpFrames() {
  155. let width = bounds.width
  156. let height = bounds.height
  157. if headerTitles != nil {
  158. headFreezeCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.freezeWidth, height: headerHeight)
  159. contentFreezeCollectionView.frame = CGRect(x: 0, y: headerHeight, width: dataManager.freezeWidth, height: height - headerHeight)
  160. contentScrollView.frame = CGRect(x: dataManager.freezeWidth, y: 0, width: width - dataManager.freezeWidth , height: height)
  161. contentScrollView.contentSize = CGSize(width: dataManager.slideWidth, height: height)
  162. headMovebleCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.slideWidth, height: headerHeight)
  163. contentMoveableCollectionView.frame = CGRect(x: 0, y: headerHeight, width: dataManager.slideWidth, height: height - headerHeight)
  164. } else {
  165. contentFreezeCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.freezeWidth, height: height)
  166. contentScrollView.frame = CGRect(x: dataManager.freezeWidth, y: 0, width: width - dataManager.freezeWidth, height: height)
  167. contentScrollView.contentSize = CGSize(width: dataManager.slideWidth, height: height)
  168. contentMoveableCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.slideWidth, height: height)
  169. }
  170. }
  171. //MARK: - 懒加载
  172. fileprivate let dataManager : AKExcelDataManager = AKExcelDataManager()
  173. fileprivate lazy var headFreezeCollectionView : UICollectionView = {
  174. return UICollectionView.init(delelate: self, datasource: self)
  175. }()
  176. fileprivate lazy var headMovebleCollectionView : UICollectionView = {
  177. return UICollectionView.init(delelate: self, datasource: self)
  178. }()
  179. fileprivate lazy var contentFreezeCollectionView : UICollectionView = {
  180. return UICollectionView.init(delelate: self, datasource: self)
  181. }()
  182. fileprivate lazy var contentMoveableCollectionView : UICollectionView = {
  183. return UICollectionView.init(delelate: self, datasource: self)
  184. }()
  185. fileprivate lazy var contentScrollView : UIScrollView = {
  186. let slideScrollView = UIScrollView()
  187. slideScrollView.bounces = false
  188. slideScrollView.showsHorizontalScrollIndicator = true
  189. slideScrollView.delegate = self
  190. return slideScrollView
  191. }()
  192. }
  193. //MARK: - UICollectionView Delegate & DataSource & collectionPrivate
  194. extension AKExcelView {
  195. func numberOfSections(in collectionView: UICollectionView) -> Int {
  196. if collectionView == headMovebleCollectionView || collectionView == headFreezeCollectionView {
  197. return 1
  198. }
  199. return contentData?.count ?? 0
  200. }
  201. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  202. guard let firstBodyData = self.contentData?.first else { return 0 }
  203. if collectionView == headFreezeCollectionView || collectionView == contentFreezeCollectionView {
  204. return leftFreezeColumn
  205. } else {
  206. if let pros = properties {
  207. return pros.count - leftFreezeColumn
  208. }
  209. let slideColumn = firstBodyData.propertyNames().count - leftFreezeColumn
  210. return slideColumn
  211. }
  212. }
  213. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  214. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AKCollectionViewCellIdentifier, for: indexPath) as! AKExcelCollectionViewCell
  215. cell.horizontalMargin = self.textMargin
  216. self.configCells(collectionView: collectionView, cell: cell, indexPath: indexPath)
  217. return cell
  218. }
  219. private func configCells(collectionView:UICollectionView ,cell:AKExcelCollectionViewCell ,indexPath: IndexPath) {
  220. var targetIndexPath = indexPath
  221. if collectionView == headFreezeCollectionView {
  222. cell.textLabel.text = headerTitles?[indexPath.row]
  223. cell.backgroundColor = headerBackgroundColor
  224. cell.textLabel.font = headerTextFontSize
  225. } else if collectionView == headMovebleCollectionView {
  226. cell.backgroundColor = headerBackgroundColor
  227. cell.textLabel.text = headerTitles?[indexPath.item + leftFreezeColumn]
  228. cell.textLabel.font = headerTextFontSize
  229. targetIndexPath = NSIndexPath(item: indexPath.item + leftFreezeColumn, section: indexPath.section) as IndexPath
  230. }else if (collectionView == contentFreezeCollectionView) {
  231. let text = dataManager.contentFreezeData[indexPath.section][indexPath.item]
  232. cell.textLabel.text = text
  233. cell.backgroundColor = contentBackgroundColor
  234. cell.textLabel.textColor = contentTextColor
  235. cell.textLabel.font = contentTextFontSize
  236. targetIndexPath = NSIndexPath(item: indexPath.item, section: indexPath.section + 1) as IndexPath
  237. } else {
  238. let text = dataManager.contentSlideData[indexPath.section][indexPath.item]
  239. cell.textLabel.text = text
  240. cell.backgroundColor = contentBackgroundColor
  241. if let contentDataColor = contentDataColor {
  242. cell.textLabel.textColor = contentDataColor[indexPath.section][indexPath.item + 1]
  243. } else {
  244. cell.textLabel.textColor = contentTextColor
  245. }
  246. cell.textLabel.font = contentTextFontSize
  247. targetIndexPath = NSIndexPath(item: indexPath.item + leftFreezeColumn, section: indexPath.section + 1) as IndexPath
  248. }
  249. self.customViewInCell(cell: cell, indexPath: targetIndexPath)
  250. self.attributeStringInCell(cell: cell, indexPath: targetIndexPath)
  251. }
  252. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  253. if (contentData?.count == 0) || self.dataManager.freezeItemSize.count == 0 || self.dataManager.slideItemSize.count == 0 {
  254. return CGSize.zero
  255. } else {
  256. if (collectionView == headFreezeCollectionView ||
  257. collectionView == contentFreezeCollectionView) {
  258. return NSCoder.cgSize(for: self.dataManager.freezeItemSize[indexPath.item])
  259. } else {
  260. return NSCoder.cgSize(for: self.dataManager.slideItemSize[indexPath.item])
  261. }
  262. }
  263. }
  264. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  265. var targetIndexPath = indexPath
  266. if collectionView == headFreezeCollectionView {
  267. } else if (collectionView == headMovebleCollectionView) {
  268. targetIndexPath = NSIndexPath.init(item: indexPath.item + leftFreezeColumn, section: indexPath.section) as IndexPath
  269. } else if (collectionView == contentFreezeCollectionView) {
  270. targetIndexPath = NSIndexPath.init(item: indexPath.item, section: indexPath.section + 1) as IndexPath
  271. } else {
  272. targetIndexPath = NSIndexPath.init(item: indexPath.item + leftFreezeColumn, section: indexPath.section + 1) as IndexPath
  273. }
  274. self.delegate?.excelView?(self, didSelectItemAt: targetIndexPath)
  275. }
  276. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
  277. if itemInsets != nil {
  278. return itemInsets!
  279. }
  280. return UIEdgeInsets.zero
  281. }
  282. }
  283. //MARK: - UISCrollViewDelegate
  284. extension AKExcelView {
  285. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  286. if scrollView != contentScrollView {
  287. contentFreezeCollectionView.contentOffset = scrollView.contentOffset
  288. contentMoveableCollectionView.contentOffset = scrollView.contentOffset
  289. if scrollView.contentOffset.y > 1 {
  290. showHorizontalDivideShadowLayer()
  291. } else {
  292. dismissHorizontalDivideShadowLayer()
  293. }
  294. }else{
  295. dPrint("scrollView.contentOffset.y is \(scrollView.contentOffset.y)")
  296. if (scrollView.contentOffset.x > 0) {
  297. showVerticalDivideShadowLayer()
  298. } else {
  299. dismissVerticalDivideShadowLayer()
  300. }
  301. CATransaction.begin()
  302. CATransaction.setDisableActions(true)
  303. veritcalShadow.transform = CATransform3DMakeTranslation(scrollView.contentOffset.x, 0, 0)
  304. CATransaction.commit()
  305. }
  306. }
  307. func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  308. if autoScrollToNearItem && scrollView == contentScrollView && !decelerate {
  309. scrollEndAnimation(scrollView: scrollView)
  310. }
  311. }
  312. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  313. if scrollView == contentScrollView && autoScrollToNearItem {
  314. scrollEndAnimation(scrollView: scrollView)
  315. }
  316. }
  317. fileprivate func scrollEndAnimation(scrollView: UIScrollView) {
  318. let offSetX = scrollView.contentOffset.x
  319. let deltaOffSets = dataManager.slideItemOffSetX.compactMap({ (offset) -> CGFloat in
  320. return abs(offset - offSetX)
  321. })
  322. var min:CGFloat = deltaOffSets[0]
  323. for i in 0 ..< deltaOffSets.count {
  324. if deltaOffSets[i] < min {
  325. min = deltaOffSets[i]
  326. }
  327. }
  328. let index = deltaOffSets.firstIndex(of: min)
  329. let slideToOffset = dataManager.slideItemOffSetX[index!]
  330. let scrollViewWidth = bounds.width - dataManager.freezeWidth
  331. dPrint("dataManager.slideWidth - offSetX - scrollViewWidth is \(dataManager.slideWidth - offSetX - scrollViewWidth)")
  332. if abs(dataManager.slideWidth - offSetX - scrollViewWidth) <= 1 { // FIXME: 不知为何,有一些误差
  333. return
  334. }
  335. UIView.animate(withDuration: 0.5, animations: {
  336. scrollView.contentOffset.x = slideToOffset
  337. })
  338. }
  339. }
  340. //MARK: - CollectionView Extension
  341. extension UICollectionView {
  342. /**
  343. * 遍历构造函数
  344. */
  345. convenience init(delelate: UICollectionViewDelegate , datasource: UICollectionViewDataSource){
  346. let flowLayout = UICollectionViewFlowLayout()
  347. flowLayout.minimumLineSpacing = 0
  348. flowLayout.minimumInteritemSpacing = 0
  349. flowLayout.headerReferenceSize = CGSize(width: kScreenWidth, height: 0)
  350. self.init(frame: CGRect.zero, collectionViewLayout: flowLayout)
  351. dataSource = datasource
  352. delegate = delelate
  353. register(AKExcelCollectionViewCell.self, forCellWithReuseIdentifier: AKCollectionViewCellIdentifier)
  354. backgroundColor = UIColor.clear
  355. showsVerticalScrollIndicator = false
  356. translatesAutoresizingMaskIntoConstraints = false
  357. if #available(iOS 11.0, *) {
  358. self.contentInsetAdjustmentBehavior = .never
  359. }
  360. }
  361. }
  362. //MARK: - AKExcelView Delegate Implemention
  363. extension AKExcelView {
  364. fileprivate func customViewInCell(cell : AKExcelCollectionViewCell , indexPath : IndexPath) {
  365. let customView = delegate?.excelView?(self, viewAt: indexPath)
  366. cell.customView = customView
  367. }
  368. fileprivate func attributeStringInCell(cell: AKExcelCollectionViewCell , indexPath : IndexPath) {
  369. let attributeString = delegate?.excelView?(self, attributedStringAt: indexPath)
  370. if attributeString != nil {
  371. cell.textLabel.attributedText = attributeString
  372. }
  373. }
  374. }