图表封装

图表封装

1. 业务

  • 可滚动的平滑曲线。
  • 选中状态: 位置在中心,点/标题/y轴字体是黑体加粗,其他为未选中状态,颜色字体淡一些。
  • 滚动到中间位置的点显示为选中状态。
  • 可以点击某个点成为选中状态,并滚动到试图中间。

2.思路

  • 滚动视图用UIScrollView横向滚动
  • 使用 CAShapeLayer + UIBezierPath 绘制曲线图表
  • 监听滚动结束后的位置,使最近的点成为选中状态。
  • 监听点击的位置,使最近的点成为选中状态。

3.细节

  • 监听UIScrollView滚动结束

滚动结束的状态有3种,1.滚动减速停止 2.滚动按压停止 3.滚动上下滑动停止。
根据 tracking、dragging、decelerating这3个属性监听停止。

   func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
       // 拖拽停止
       if !decelerate {
           let dragToDragStop = scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating
           if dragToDragStop {
               self.cgo_scrollViewDidEndScroll(scrollView)
           }
       }
   }
   
   func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
       /// 快速滑动自由停止/按压停止
       let scrollToScrollStop = !scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating
       if scrollToScrollStop {
           self.cgo_scrollViewDidEndScroll(scrollView)
       }
   }
  • UIScrollView点击事件

UIScrollView对于touch事件的接收处理原理:UIScrollView重载hitTest 方法,并总会返回itself 。所以所有的touch 事件都会进入到它自己里面去了,所以UIScrollView及其子视图是不响应touchBegan事件的
这里有两种方式,第一种给UIScrollView添加点击手势。在手势结束是处理点击事件

       let tap = UITapGestureRecognizer(target: self, action: #selector(tapEvent(_:)))
       self.chartView.addGestureRecognizer(tap)
   
   @objc func tapEvent(_ gestureRecognizer: UIGestureRecognizer) {
       switch gestureRecognizer.state {
       case .ended:
           do {
               let point = gestureRecognizer.location(in: self.chartView)
               let index = self.calculatePosition(offset: point.x)
               guard let pointIndex = index else {
                   return
               }
               
               debugPrint("ponitIndex === \(pointIndex)")
               self.updateRow(index: pointIndex)
               //        self.lastRow = ponitIndex
               self.adustMidPosition(isDrag: false)
               self.delegate?.chartView(self, didSelectedRowWithIndex: pointIndex)
           }
           break
       default:
           break
       }
   }

第二种是重写touchbegan一系列事件,单独处理点击事件和移动事件,点击事件自己处理,移动事件交给UIScrollView滚动结束处理,但是这里长按也会被统计为移动事件,导致一个小问题。

class CGOChartScrollView: UIScrollView {

   public var isDrag : Bool = false
   public var tapEvent : ((Set)-> Void)?
   override func touchesBegan(_ touches: Set, with event: UIEvent?) {
       super.touchesBegan(touches, with: event)
   }
   
   override func touchesMoved(_ touches: Set, with event: UIEvent?) {
       self.isDrag = true
       super.touchesMoved(touches, with: event)
   }
   
   override func touchesEnded(_ touches: Set, with event: UIEvent?) {
       if self.isDrag {
           super.touchesEnded(touches, with: event)
       } else {
           
           if let event = self.tapEvent {
               event(touches)
           }
       }
       self.isDrag = false
   }
}
  • UIScrollView setContentOffset:animated与contentOffset的区别

setContentOffset有两种方法:setContentOffset:和setContentOffset:animated:
但是两者还是有点差异的:
setContentOffset:animated: 这种方法,无论animated为YES还是NO, 都会等待scrollView的滚动结束以后才会执行,也就是当isDragging和isDecelerating为YES的时候,会等待滚动完成才执行上面的方法。
setContentOffset:这种方法则不受scrollView是否正在滚动的限制。
所以我在滚动结束后和点击事件使用的动画是不同的,需要判断是否是拖拽手势isDrag
设置setContentOffset的动画

       if isDrag {
           self.chartView.setContentOffset(CGPoint(x: m_offset, y: 0), animated: true)
       } else {
           UIView.animate(withDuration: 0.3, delay: 0, options: UIView.AnimationOptions.curveEaseInOut, animations: {
               self.chartView.contentOffset = CGPoint(x: m_offset, y: 0)
           }, completion: nil)
       }
  • 滑动或者点击后选中状态的绘制。
  • 第一次进入时只绘制可见部分,后面滚动到什么位置就绘制到哪里。

4.接口

  • 数据接口设计

避免数据的耦合,可以使用多种方式,比如UITableVIew的dataSource,我这里使用强制类型CGOChartData

class CGOChartDataSet : NSObject {
   public var title : String = ""
   public var prefix : String = "R$"
   public var yAixsText : String = ""
   public var isSelected : Bool = false
   
}

class CGOChartData : NSObject {
   public var dataSet : CGOChartDataSet?
   public var point : CGPoint = CGPoint.zero
}

dataSet是对数据的设置
使用的时候需要进行转换

       var data : Array = Array()
       for (index,value) in a.enumerated() {
           let d = CGOChartData()
           let set = CGOChartDataSet()
           d.point = self.chartView.getPoint(index: index, value: value.consumption, count: 6)
           set.title = "\(value.consumption)"
           set.isSelected = value.isSelected
           set.yAixsText = "\(value.month)"
           d.dataSet = set
           data.append(d)
       }
       self.chartView.points = data
  • 对外接口设计

选中状态完成时的接口,这里包括点击选中和滚动后选中

// 接口
protocol CGOLineChartViewProtocol : NSObjectProtocol {
   // 选中结束事件
   func chartView(_ chartView: CGOLineChartView, didSelectedRowWithIndex: NSInteger) -> Void
   
}

5.优化

6.性能

你可能感兴趣的:(图表封装)