ios - collectionView 中的 CAShapelayer 在 reloadData() 上添加奇怪的动画

标签 ios swift cashapelayer collectionview

我构建了一个 collectionView,它在每个单元格中显示一个圆形图(以展示百分比)。为此,我在 CustomCell 中转换的子类 (CircularGraph) 中绘制 CAShapelayer。

我遇到的问题是,每当我重新加载我的 collectionView 时,CAShapelayers 都会重新绘制到它们的 strokeEnd 位置,并在这样做时添加不需要的动画。看起来他们是根据上一层的 strokeEnd 值绘制的。这让我相信这与我的子类无法转换图形的离散版本有关。

为了演示这个问题,我构建了一个小型演示项目,可用 here .只需点击重新加载按钮即可查看我指的是什么。每当加载 View 时也会发生这种情况,例如切换选项卡时(我想这是有道理的,因为 collectionView 会再次加载它的数据)。

这是 Collection View :

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)
  }
}

最佳答案

我找到了一个基于 CATransaction 的修复,你可以遵循这个想法:

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

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

时将其设置为 true

关于ios - collectionView 中的 CAShapelayer 在 reloadData() 上添加奇怪的动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48292820/

相关文章:

ios - 如何使用 Google Drive API v3 获取我的云端硬盘中的文件?

ios - 通过字符串设置 Objective-C 属性

ios - DatePicker - 如何用三个字符而不是数字显示日期月份名称

ios - 如何在使用 CAShapeLayer - Swift 绘制的路径上标记点?

ios - 如何使用 Realm Swift 将旧属性迁移到新对象中

ios - Adcolony 无法找到当前可见的 UIViewController

swift - 使 UIButton 占屏幕大小的百分比

ios - 无法将类型 '__NSCFString' 的值转换为 'NSDictionary'

ios - CAShapeLayer 出现在 View 之前

ios - 如何绘制不同颜色的填充路径/形状