我已经构建了一个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)
}
}
答案 0 :(得分:2)
我找到了基于CATransaction
的修补程序,您可以遵循以下想法:
var progressLayerStrokeEnd: CGFloat = 1 {
didSet {
CATransaction.begin()
CATransaction.setDisableActions(true)
progressLayer.strokeEnd = progressLayerStrokeEnd
CATransaction.commit()
}
}
基本上,每次修改CAShapeLayer
时,都应禁用隐式CAShapeLayer动画。因此,您可以根据需要调整setDisableActions
的标记,例如:在您调用true
时设置reloadData