如何使 UIBezierPath 从中心填充

Posted

技术标签:

【中文标题】如何使 UIBezierPath 从中心填充【英文标题】:How to make UIBezierPath to fill from the center 【发布时间】:2019-07-01 20:02:12 【问题描述】:

如何制作从中心开始绘制并向左右扩展的 UIBezierPath?

我当前的解决方案不是使用 UIBezierPath,而是使用另一个 UIView 中的 UIView。子视图位于其父视图的中心,当我需要扩展子视图时,我会调整子视图的宽度。然而,这个解决方案远非完美,因为视图位于 UIStackView 内部,而堆栈视图位于 UICollectionView 单元格中,有时无法正确获取宽度。

   override func layoutSubviews() 
    super.layoutSubviews()
    progressView.cornerRadius = cornerRadius
    clipsToBounds = true
    addSubview(progressView)
    NSLayoutConstraint.activate([
        progressView.topAnchor.constraint(equalTo: topAnchor),
        progressView.bottomAnchor.constraint(equalTo: bottomAnchor),
        progressView.centerXAnchor.constraint(equalTo: centerXAnchor)
    ])
    let width = frame.width
    let finalProgress = CGFloat(progress) * width
    let widthConstraint = progressView.widthAnchor.constraint(equalToConstant: finalProgress)
    widthConstraint.priority = UILayoutPriority(rawValue: 999)
    widthConstraint.isActive = true

进度是 double 类型的类属性,当它被设置时,我调用 layoutIfNeeded。

我也尝试调用 setNeedsLayout 和 layoutIfNeeded 但它不能解决问题。 SetNeedsDisplay 和 invalidateIntrinzicSize 也不起作用。

现在,我想使用贝塞尔路径,但问题是它从给定点开始并从左到右绘制。问题是:如何制作居中的 UIBezierPath 以及当我增加宽度时它会扩展(保持在中心)

这是我想要实现的图像。

【问题讨论】:

尽管如此,我会这样做的方式正是您一开始所说的那样,一种以自己的宽度绘制水平形状的视图。 问题是滚动几次后,它会停止更新,或者由于某种原因获取不正确,因为它不会每次都调用 layoutSubviews。我尝试强制 layoutSubviews 但它不起作用。 那将是您未在问题中透露的其他原因。 “滚动几次后”表明您没有考虑单元重用。但是很难说,因为你没有展示相关代码。 【参考方案1】:

有很多选择。但这里有一些:

    strokeStartCABasicAnimationCAShapeLayerstrokeEnd,即:

    创建CAShapeLayer,其path 是最终的UIBezierPath(全宽),但其strokeStartstrokeEnd 均为0.5(即中途开始,中途结束,即什么都看不见但是)。

    func configure() 
        shapeLayer.strokeColor = strokeColor.cgColor
        shapeLayer.lineCap = .round
        setProgress(0, animated: false)
    
    
    func updatePath() 
        let path = UIBezierPath()
        path.move(to: CGPoint(x: bounds.minX + lineWidth, y: bounds.midY))
        path.addLine(to: CGPoint(x: bounds.maxX - lineWidth, y: bounds.midY))
        shapeLayer.path = path.cgPath
        shapeLayer.lineWidth = lineWidth
    
    

    然后,在CABasicAnimation 中,将strokeStart 更改为0strokeEnd 更改为1 以实现动画从中间向左和向右增长。例如:

    func setProgress(_ progress: CGFloat, animated: Bool = true) 
        let percent = max(0, min(1, progress))
        let strokeStart = (1 - percent) / 2
        let strokeEnd = 1 - (1 - percent) / 2
    
        if animated 
            let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart")
            strokeStartAnimation.fromValue = shapeLayer.strokeStart
            strokeStartAnimation.toValue = strokeStart
    
            let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
            strokeEndAnimation.fromValue = shapeLayer.strokeEnd
            strokeEndAnimation.toValue = strokeEnd
    
            let animation = CAAnimationGroup()
            animation.animations = [strokeStartAnimation, strokeEndAnimation]
    
            shapeLayer.strokeStart = strokeStart
            shapeLayer.strokeEnd = strokeEnd
    
            layer.add(animation, forKey: nil)
         else 
            shapeLayer.strokeStart = strokeStart
            shapeLayer.strokeEnd = strokeEnd
        
    
    

    pathCAShapeLayer 的动画

    拥有创建UIBezierPath的方法:

    func setProgress(_ progress: CGFloat, animated: Bool = true) 
        self.progress = progress
    
        let cgPath = path(for: progress).cgPath
    
        if animated 
            let animation = CABasicAnimation(keyPath: "path")
            animation.fromValue = shapeLayer.path
            animation.toValue = cgPath
    
            shapeLayer.path = cgPath
    
            layer.add(animation, forKey: nil)
         else 
            shapeLayer.path = cgPath
        
    
    

    在哪里

    func path(for progress: CGFloat) -> UIBezierPath 
        let path = UIBezierPath()
        let percent = max(0, min(1, progress))
        let width = bounds.width - lineWidth
        path.move(to: CGPoint(x: bounds.midX - width * percent / 2 , y: bounds.midY))
        path.addLine(to: CGPoint(x: bounds.midX + width * percent / 2, y: bounds.midY))
        return path
    
    

    当您创建CAShapeLayer 调用setProgress 时,其值为0,并且没有动画;和

    当你想制作动画时,调用setProgress,值为1,但带有动画。

    放弃UIBezierPath/CAShapeLayer 接近并使用UIViewUIView 基于块的动画:

    使用backgroundColor 创建短UIView,其层的cornerRadius 是视图高度的一半。

    定义约束,使视图的宽度为零。例如。您可以定义 centerXanchor 并将其放置在您想要的位置,并且 widthAnchor 为零。

    通过将现有的widthAnchorconstant 设置为您想要的任何宽度并将layoutIfNeeded 放置在UIView 动画块中来为约束设置动画。

【讨论】:

以上是关于如何使 UIBezierPath 从中心填充的主要内容,如果未能解决你的问题,请参考以下文章

如何使 UIBezierPath 从视图中间开始(目前它是从视图的左角开始)

如何填充 UIBezierPath 中的重叠部分?

如何为 UIBezierPath 上的填充颜色变化设置动画?

如何在 UIBezierPath 弧中获得半径的中心点?

使用 UIBezierPath 绘制 100 多个不同位置的圆圈

iOS:如何为uibezierpath中的填充设置动画?