Setting toValue for CAShapeLayer animation from UI

2019-08-25 02:33发布

问题:

I have the following parts: - My main view is a UIViewController with a UICollectionView - The cell for the UICollectionView - A subclass of the UIView to build a CAShapeLayer with an CABasicAnimation

In my main view I have a UICollectionView which renders a bunch of cells with labels etc. It also is showing a progress graph.

In my subclass ProgressCirclePath() I am drawing a CAShapeLayer which is acting as the progress graph rendered in each cell of my UICollectionView.

I have been able to pass data to each cell, e.g. the labels as well as the CAShapeLayer strokeEnd values.

Everything is fine until I try to add a CABasicAnimation to my path. In this case I am not able to set the value for the animations toValue. Testing using the print console reveals that the value is available in my UICollectionView but not in the animation block in my subClass (which is where it simply returns nil). I have tried simply setting the toValue as well as creating a variable within my ProgressCirlePath and setting it from the UICollectionView. Neither worked.

I'd appreciate any hints on why this is happening and how to solve this. Thanks!!

Within my UICollectionView:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
      let cell = decksCollectionView.dequeueReusableCell(withReuseIdentifier: "DeckCell", for: indexPath) as! DeckCell

  cell.creatorLabel.text = deckCellCreator[indexPath.item]
  cell.titleLabel.text = deckCellTitle[indexPath.item]
  cell.progressLabel.text = "\(deckCellCompletionPercentage[indexPath.item])%"

  cell.progressGraphView.animation.toValue = CGFloat(deckCellCompletionPercentage[indexPath.item])/100

  return cell
}

The setup within my cell class:

let progressGraphView: ProgressCirclePath = {
    let circlePath = ProgressCirclePath(frame: CGRect(x:0, y:0, width: 86, height: 86))
    circlePath.progressLayer.position = circlePath.center
    circlePath.progressBackgroundLayer.position = circlePath.center
    circlePath.translatesAutoresizingMaskIntoConstraints = false
    return circlePath
}()

And here my ProgressCirclePath()

class ProgressCirclePath: UIView {
let progressLayer = CAShapeLayer()
let progressBackgroundLayer = CAShapeLayer()
let animation = CABasicAnimation(keyPath: "strokeEnd")
//    var percentageValue = CGFloat()


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

    layer.addSublayer(progressBackgroundLayer)
    layer.addSublayer(progressLayer)

    let circularPath = UIBezierPath(arcCenter: .zero, radius: 43, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)

    progressBackgroundLayer.path = circularPath.cgPath
    progressBackgroundLayer.lineWidth = 10
    progressBackgroundLayer.strokeStart = 0
    progressBackgroundLayer.strokeEnd = 1
    progressBackgroundLayer.strokeColor = UIColor(red: 221/255.0, green: 240/255.0, blue: 226/255.0, alpha: 1.0).cgColor
    progressBackgroundLayer.fillColor = UIColor.clear.cgColor
    progressBackgroundLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)

    progressLayer.path = circularPath.cgPath
    progressLayer.lineWidth = 10
    progressLayer.lineCap = kCALineCapRound
    progressLayer.strokeColor = UIColor(red: 72/255.0, green: 172/255.0, blue: 104/255.0, alpha: 1.0).cgColor
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)


    animation.duration = 1
    animation.fromValue = 0
    // animation.toValue = percentageValue
    animation.fillMode = kCAFillModeForwards
    animation.isRemovedOnCompletion = false
    progressLayer.add(animation, forKey: "animateGraph")
    print("animation.toValue \(animation.toValue)")
}

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

}

回答1:

You're handling the injection of data and animation too early in the lifecycle of the custom view. Instead of handling them in the object's initializer, move them to a later and more appropriate method, such as layoutSubviews:

override open func layoutSubviews() {
    super.layoutSubviews()

    // handle post-init stuff here, like animations and passing in data
    // when it doesn't get passed in on init

}