集合中的CAShapelayer在reloadData()

时间:2018-01-17 02:42:20

标签: ios swift cashapelayer collectionview

我已经构建了一个collectionView,它在每个单元格中显示一个圆形图(以显示百分比)。为了做到这一点,我在一个子类(CircularGraph)中绘制CAShapelayer,我在我的CustomCell中投射。

我遇到的问题是,每当我重新加载我的collectionView时,CAShapelayers都会重绘到它们的strokeEnd位置,并在这样做时添加不需要的动画。它们似乎是从前一层的strokeEnd值的值中提取的。这让我相信这与我的子类无法构建图的离散版本有关。

为了演示此问题,我构建了一个小型演示项目,可用here。只需点击重新加载按钮即可查看我所指的内容。每当加载视图时也会发生这种情况,例如当切换标签时(我认为这是有道理的,因为collectionView会再次加载它的数据)。

这是collectionView:

class CellsController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

  let graphValues:[CGFloat] = [0.12, 0.35, 0.14, 1, 0.89]

  override func viewDidLoad() {
    super.viewDidLoad()
    print("Showing cellsController")
    collectionView?.backgroundColor = .lightGray
    navigationItem.title = "Cells"
    navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Reload", style: .plain, target: self, action: #selector(didPressReload))
    collectionView?.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
  }

  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
    cell.graph.progressLayerStrokeEnd = graphValues[indexPath.row]
    return cell
  }

  override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return graphValues.count
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width, height: 156)
  }

  @objc func didPressReload() {
    collectionView?.reloadData()
  }
}

这是投射图表的CustomCell:

class CustomCell: UICollectionViewCell {

  let graph: CircularGraph = {
    let graph = CircularGraph()
    graph.translatesAutoresizingMaskIntoConstraints = false
    return graph
  }()

  override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = .white

    addSubview(graph)
    graph.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
    graph.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    graph.heightAnchor.constraint(equalToConstant: 100).isActive = true
    graph.widthAnchor.constraint(equalToConstant: 100).isActive = true
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

这是图的子类:

class CircularGraph: UIView {

  //All layers
  let trackLayer = CAShapeLayer()
  let progressLayer = CAShapeLayer()

  //Animation values
  var percentageValue = CGFloat()

  //Line width
  var lineWidth: CGFloat = 15 { didSet { updatePath() } }

  //Fill colors
  var trackLayerFillColor: UIColor = .clear { didSet { trackLayer.fillColor = trackLayerFillColor.cgColor } }
  var progressLayerFillColor: UIColor = .clear { didSet { progressLayer.fillColor = progressLayerFillColor.cgColor } }

  //Stroke colors
  var trackStrokeColor: UIColor = UIColor.lightGray { didSet { trackLayer.strokeColor = trackStrokeColor.cgColor  } }
  var progressLayerStrokeColor: UIColor = UIColor.green { didSet { progressLayer.strokeColor = progressLayerStrokeColor.cgColor } }

  //Stroke start and end
  var trackLayerStrokeStart: CGFloat = 0 { didSet { trackLayer.strokeStart = trackLayerStrokeStart } }
  var progressLayerStrokeStart: CGFloat = 0 { didSet { progressLayer.strokeStart = progressLayerStrokeStart } }

  var trackLayerStrokeEnd:   CGFloat = 1 { didSet { trackLayer.strokeEnd = trackLayerStrokeEnd } }
  var progressLayerStrokeEnd:   CGFloat = 1 { didSet { progressLayer.strokeEnd = progressLayerStrokeEnd } }

  override init(frame: CGRect) {
    super.init(frame: frame)
    configure()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    configure()
  }

  override func layoutSubviews() {
    super.layoutSubviews()
    updatePath()
  }  

  func configure() {
    trackLayer.strokeColor = trackStrokeColor.cgColor
    trackLayer.fillColor = trackLayerFillColor.cgColor
    trackLayer.strokeStart = trackLayerStrokeStart
    trackLayer.strokeEnd  = trackLayerStrokeEnd

    progressLayer.strokeColor = progressLayerStrokeColor.cgColor
    progressLayer.fillColor = progressLayerFillColor.cgColor
    progressLayer.strokeStart = progressLayerStrokeStart
    progressLayer.strokeEnd  = progressLayerStrokeEnd

    layer.addSublayer(trackLayer)
    layer.addSublayer(progressLayer)
  }

  func updatePath() {
    //The actual calculation for the circular graph
    let arcCenter = CGPoint(x: bounds.midX, y: bounds.midY)
    let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
    let circularPath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)

    trackLayer.path = circularPath.cgPath
    trackLayer.lineWidth = lineWidth

    progressLayer.path = circularPath.cgPath
    progressLayer.lineWidth = lineWidth
    progressLayer.lineCap = kCALineCapRound

    //Set the frame in order to rotate the outer circular paths to start at 12 o'clock
    trackLayer.transform = CATransform3DIdentity
    trackLayer.frame = bounds
    trackLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)

    progressLayer.transform = CATransform3DIdentity
    progressLayer.frame = bounds
    progressLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
  }
}

1 个答案:

答案 0 :(得分:2)

我找到了基于CATransaction的修补程序,您可以遵循以下想法:

var progressLayerStrokeEnd: CGFloat = 1 {
    didSet {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        progressLayer.strokeEnd = progressLayerStrokeEnd
        CATransaction.commit()
    }
}

基本上,每次修改CAShapeLayer时,都应禁用隐式CAShapeLayer动画。因此,您可以根据需要调整setDisableActions的标记,例如:在您调用true时设置reloadData