// // ExcelViewSwifty.swift // YunYingSwift // // Created by AlasKu on 17/2/10. // Copyright © 2017年 innostic. All rights reserved. // import UIKit import NVActivityIndicatorView let AKCollectionViewCellIdentifier = "AKCollectionView_Cell" let AKCollectionViewHeaderIdentifier = "AKCollectionView_Header" @objc protocol AKExcelViewDelegate : NSObjectProtocol { @objc optional func excelView(_ excelView: AKExcelView, didSelectItemAt indexPath: IndexPath) @objc optional func excelView(_ excelView: AKExcelView, viewAt indexPath: IndexPath) -> UIView? @objc optional func excelView(_ excelView: AKExcelView, attributedStringAt indexPath: IndexPath) -> NSAttributedString? } class AKExcelView: UIView , UICollectionViewDelegate , UICollectionViewDataSource , UIScrollViewDelegate , UICollectionViewDelegateFlowLayout{ /// Delegate weak var delegate : AKExcelViewDelegate? /// CellTextMargin var textMargin : CGFloat = 5 /// Cell Max width var itemMaxWidth : CGFloat = 200 /// cell Min width var itemMinWidth : CGFloat = 50 /// cell heihth var itemHeight : CGFloat = 44 /// header Height var headerHeight : CGFloat = 44 /// header BackgroundColor var headerBackgroundColor : UIColor = UIColor.lightGray /// header Text Color var headerTextColor : UIColor = UIColor.black /// header Text Font var headerTextFontSize : UIFont = UIFont.systemFont(ofSize: 15) /// contenTCell TextColor var contentTextColor : UIColor = UIColor.black /// content backgroung Color var contentBackgroundColor : UIColor = UIColor.white /// content Text Font var contentTextFontSize : UIFont = UIFont.systemFont(ofSize: 13) /// letf freeze column var leftFreezeColumn : Int = 1 /// header Titles var headerTitles : [String]? /// content Data var contentData : Array? /// 颜色支持 var contentDataColor: [[UIColor]]? /// set Column widths var columnWidthSetting : Dictionary? /// CelledgeInset var itemInsets : UIEdgeInsets? /// showsProperties var properties : Array? /// autoScrollItem default is false var autoScrollToNearItem = false /// if set, there is no loading view when reload datas. default is true var isNeedLoadingView = true fileprivate lazy var horizontalShadow : CAShapeLayer = { //水平阴影 let s = CAShapeLayer() s.strokeColor = UIColor.lightGray.cgColor s.shadowColor = UIColor.black.cgColor s.shadowOffset = CGSize.init(width: 2, height: 0) s.shadowOpacity = 1 return s }() fileprivate lazy var veritcalShadow : CAShapeLayer = { //垂直阴影 let s = CAShapeLayer() s.strokeColor = UIColor.lightGray.cgColor s.shadowColor = UIColor.black.cgColor s.shadowOffset = CGSize.init(width: 0, height: 3) s.shadowOpacity = 1 return s }() // MARK: - Public Method override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } override func layoutSubviews() { super.layoutSubviews() setUpFrames() } func reloadData() { if isNeedLoadingView { // 开启Loading NVActivityIndicatorPresenter.sharedInstance.startAnimating(ActivityData(message: "数据加载中...", color: UIColor.loding()), NVActivityIndicatorView.DEFAULT_FADE_IN_ANIMATION) } DispatchQueue.global().sync { self.dataManager.caculateData() DispatchQueue.main.async { if self.isNeedLoadingView { NVActivityIndicatorPresenter.sharedInstance.stopAnimating(NVActivityIndicatorView.DEFAULT_FADE_OUT_ANIMATION) } self.setUpFrames() self.headFreezeCollectionView.reloadData() self.headMovebleCollectionView.reloadData() self.contentFreezeCollectionView.reloadData() self.contentMoveableCollectionView.reloadData() } } } func sizeForItem(item: Int) -> CGSize { if item < leftFreezeColumn { return NSCoder.cgSize(for: self.dataManager.freezeItemSize[item]) } else { return NSCoder.cgSize(for: self.dataManager.slideItemSize[item - leftFreezeColumn]) } } //MARK: - Private Method private func setup() { dataManager.excelView = self clipsToBounds = true addSubview(headFreezeCollectionView) addSubview(contentFreezeCollectionView) addSubview(contentScrollView) contentScrollView.addSubview(headMovebleCollectionView) contentScrollView.addSubview(contentMoveableCollectionView) contentMoveableCollectionView.contentInset = UIEdgeInsets(top: -1, left: 0, bottom: 0, right: 0) contentFreezeCollectionView.contentInset = UIEdgeInsets(top: -1, left: 0, bottom: 0, right: 0) layer.addSublayer(horizontalShadow) contentScrollView.layer.addSublayer(veritcalShadow) } fileprivate func showHorizontalDivideShadowLayer() { if horizontalShadow.path == nil { let path = UIBezierPath() path.move(to: CGPoint(x: 0, y: headerHeight)) path.addLine(to: CGPoint(x: min(self.bounds.width, self.dataManager.freezeWidth + self.dataManager.slideWidth), y: headerHeight)) path.lineWidth = 0.5 horizontalShadow.path = path.cgPath } } fileprivate func dismissHorizontalDivideShadowLayer() { horizontalShadow.path = nil } fileprivate func showVerticalDivideShadowLayer() { if veritcalShadow.path == nil { let path = UIBezierPath() let heigh = contentScrollView.contentSize.height // + headFreezeCollectionView.contentSize.height path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: 0, y: heigh)) path.lineWidth = 0.5 veritcalShadow.path = path.cgPath } } fileprivate func dismissVerticalDivideShadowLayer() { veritcalShadow.path = nil } fileprivate func setUpFrames() { let width = bounds.width let height = bounds.height if headerTitles != nil { headFreezeCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.freezeWidth, height: headerHeight) contentFreezeCollectionView.frame = CGRect(x: 0, y: headerHeight, width: dataManager.freezeWidth, height: height - headerHeight) contentScrollView.frame = CGRect(x: dataManager.freezeWidth, y: 0, width: width - dataManager.freezeWidth , height: height) contentScrollView.contentSize = CGSize(width: dataManager.slideWidth, height: height) headMovebleCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.slideWidth, height: headerHeight) contentMoveableCollectionView.frame = CGRect(x: 0, y: headerHeight, width: dataManager.slideWidth, height: height - headerHeight) } else { contentFreezeCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.freezeWidth, height: height) contentScrollView.frame = CGRect(x: dataManager.freezeWidth, y: 0, width: width - dataManager.freezeWidth, height: height) contentScrollView.contentSize = CGSize(width: dataManager.slideWidth, height: height) contentMoveableCollectionView.frame = CGRect(x: 0, y: 0, width: dataManager.slideWidth, height: height) } } //MARK: - 懒加载 fileprivate let dataManager : AKExcelDataManager = AKExcelDataManager() fileprivate lazy var headFreezeCollectionView : UICollectionView = { return UICollectionView.init(delelate: self, datasource: self) }() fileprivate lazy var headMovebleCollectionView : UICollectionView = { return UICollectionView.init(delelate: self, datasource: self) }() fileprivate lazy var contentFreezeCollectionView : UICollectionView = { return UICollectionView.init(delelate: self, datasource: self) }() fileprivate lazy var contentMoveableCollectionView : UICollectionView = { return UICollectionView.init(delelate: self, datasource: self) }() fileprivate lazy var contentScrollView : UIScrollView = { let slideScrollView = UIScrollView() slideScrollView.bounces = false slideScrollView.showsHorizontalScrollIndicator = true slideScrollView.delegate = self return slideScrollView }() } //MARK: - UICollectionView Delegate & DataSource & collectionPrivate extension AKExcelView { func numberOfSections(in collectionView: UICollectionView) -> Int { if collectionView == headMovebleCollectionView || collectionView == headFreezeCollectionView { return 1 } return contentData?.count ?? 0 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { guard let firstBodyData = self.contentData?.first else { return 0 } if collectionView == headFreezeCollectionView || collectionView == contentFreezeCollectionView { return leftFreezeColumn } else { if let pros = properties { return pros.count - leftFreezeColumn } let slideColumn = firstBodyData.propertyNames().count - leftFreezeColumn return slideColumn } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AKCollectionViewCellIdentifier, for: indexPath) as! AKExcelCollectionViewCell cell.horizontalMargin = self.textMargin self.configCells(collectionView: collectionView, cell: cell, indexPath: indexPath) return cell } private func configCells(collectionView:UICollectionView ,cell:AKExcelCollectionViewCell ,indexPath: IndexPath) { var targetIndexPath = indexPath if collectionView == headFreezeCollectionView { cell.textLabel.text = headerTitles?[indexPath.row] cell.backgroundColor = headerBackgroundColor cell.textLabel.font = headerTextFontSize } else if collectionView == headMovebleCollectionView { cell.backgroundColor = headerBackgroundColor cell.textLabel.text = headerTitles?[indexPath.item + leftFreezeColumn] cell.textLabel.font = headerTextFontSize targetIndexPath = NSIndexPath(item: indexPath.item + leftFreezeColumn, section: indexPath.section) as IndexPath }else if (collectionView == contentFreezeCollectionView) { let text = dataManager.contentFreezeData[indexPath.section][indexPath.item] cell.textLabel.text = text cell.backgroundColor = contentBackgroundColor cell.textLabel.textColor = contentTextColor cell.textLabel.font = contentTextFontSize targetIndexPath = NSIndexPath(item: indexPath.item, section: indexPath.section + 1) as IndexPath } else { let text = dataManager.contentSlideData[indexPath.section][indexPath.item] cell.textLabel.text = text cell.backgroundColor = contentBackgroundColor if let contentDataColor = contentDataColor { cell.textLabel.textColor = contentDataColor[indexPath.section][indexPath.item + 1] } else { cell.textLabel.textColor = contentTextColor } cell.textLabel.font = contentTextFontSize targetIndexPath = NSIndexPath(item: indexPath.item + leftFreezeColumn, section: indexPath.section + 1) as IndexPath } self.customViewInCell(cell: cell, indexPath: targetIndexPath) self.attributeStringInCell(cell: cell, indexPath: targetIndexPath) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { if (contentData?.count == 0) || self.dataManager.freezeItemSize.count == 0 || self.dataManager.slideItemSize.count == 0 { return CGSize.zero } else { if (collectionView == headFreezeCollectionView || collectionView == contentFreezeCollectionView) { return NSCoder.cgSize(for: self.dataManager.freezeItemSize[indexPath.item]) } else { return NSCoder.cgSize(for: self.dataManager.slideItemSize[indexPath.item]) } } } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { var targetIndexPath = indexPath if collectionView == headFreezeCollectionView { } else if (collectionView == headMovebleCollectionView) { targetIndexPath = NSIndexPath.init(item: indexPath.item + leftFreezeColumn, section: indexPath.section) as IndexPath } else if (collectionView == contentFreezeCollectionView) { targetIndexPath = NSIndexPath.init(item: indexPath.item, section: indexPath.section + 1) as IndexPath } else { targetIndexPath = NSIndexPath.init(item: indexPath.item + leftFreezeColumn, section: indexPath.section + 1) as IndexPath } self.delegate?.excelView?(self, didSelectItemAt: targetIndexPath) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { if itemInsets != nil { return itemInsets! } return UIEdgeInsets.zero } } //MARK: - UISCrollViewDelegate extension AKExcelView { func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView != contentScrollView { contentFreezeCollectionView.contentOffset = scrollView.contentOffset contentMoveableCollectionView.contentOffset = scrollView.contentOffset if scrollView.contentOffset.y > 1 { showHorizontalDivideShadowLayer() } else { dismissHorizontalDivideShadowLayer() } }else{ dPrint("scrollView.contentOffset.y is \(scrollView.contentOffset.y)") if (scrollView.contentOffset.x > 0) { showVerticalDivideShadowLayer() } else { dismissVerticalDivideShadowLayer() } CATransaction.begin() CATransaction.setDisableActions(true) veritcalShadow.transform = CATransform3DMakeTranslation(scrollView.contentOffset.x, 0, 0) CATransaction.commit() } } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if autoScrollToNearItem && scrollView == contentScrollView && !decelerate { scrollEndAnimation(scrollView: scrollView) } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { if scrollView == contentScrollView && autoScrollToNearItem { scrollEndAnimation(scrollView: scrollView) } } fileprivate func scrollEndAnimation(scrollView: UIScrollView) { let offSetX = scrollView.contentOffset.x let deltaOffSets = dataManager.slideItemOffSetX.compactMap({ (offset) -> CGFloat in return abs(offset - offSetX) }) var min:CGFloat = deltaOffSets[0] for i in 0 ..< deltaOffSets.count { if deltaOffSets[i] < min { min = deltaOffSets[i] } } let index = deltaOffSets.firstIndex(of: min) let slideToOffset = dataManager.slideItemOffSetX[index!] let scrollViewWidth = bounds.width - dataManager.freezeWidth dPrint("dataManager.slideWidth - offSetX - scrollViewWidth is \(dataManager.slideWidth - offSetX - scrollViewWidth)") if abs(dataManager.slideWidth - offSetX - scrollViewWidth) <= 1 { // FIXME: 不知为何,有一些误差 return } UIView.animate(withDuration: 0.5, animations: { scrollView.contentOffset.x = slideToOffset }) } } //MARK: - CollectionView Extension extension UICollectionView { /** * 遍历构造函数 */ convenience init(delelate: UICollectionViewDelegate , datasource: UICollectionViewDataSource){ let flowLayout = UICollectionViewFlowLayout() flowLayout.minimumLineSpacing = 0 flowLayout.minimumInteritemSpacing = 0 flowLayout.headerReferenceSize = CGSize(width: kScreenWidth, height: 0) self.init(frame: CGRect.zero, collectionViewLayout: flowLayout) dataSource = datasource delegate = delelate register(AKExcelCollectionViewCell.self, forCellWithReuseIdentifier: AKCollectionViewCellIdentifier) backgroundColor = UIColor.clear showsVerticalScrollIndicator = false translatesAutoresizingMaskIntoConstraints = false if #available(iOS 11.0, *) { self.contentInsetAdjustmentBehavior = .never } } } //MARK: - AKExcelView Delegate Implemention extension AKExcelView { fileprivate func customViewInCell(cell : AKExcelCollectionViewCell , indexPath : IndexPath) { let customView = delegate?.excelView?(self, viewAt: indexPath) cell.customView = customView } fileprivate func attributeStringInCell(cell: AKExcelCollectionViewCell , indexPath : IndexPath) { let attributeString = delegate?.excelView?(self, attributedStringAt: indexPath) if attributeString != nil { cell.textLabel.attributedText = attributeString } } }