CAShapeLayer() 绘制奇怪的线条/路径

Tim*_*Tim 2 cabasicanimation cashapelayer ios swift

我有一个CAShapeLayer()顶部有渐变的动画,但不知何故它看起来如下图所示: 图像

怎么看起来是这个样子?

我的代码:

override func viewDidLayoutSubviews() {
    displayLine()
}

override func viewDidAppear(_ animated: Bool) {
    animateStroke()
}

func displayLine() {
    let trackLayer = CAShapeLayer()
    let rect = CGRect(x: topView.frame.width * 0.15, y: topView.frame.size.height / 1.5, width: topView.frame.width * 0.7, height: 2)
    let path = UIBezierPath(roundedRect: rect, cornerRadius: 1)

    trackLayer.path = path.cgPath
    trackLayer.strokeColor = UIColor.groupTableViewBackground.cgColor
    trackLayer.lineWidth = 3
    trackLayer.fillColor = UIColor.clear.cgColor

    shapeLayer.path = path.cgPath
    shapeLayer.strokeColor = UIColor.green.cgColor
    shapeLayer.lineWidth = 4
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeEnd = 0

    topView.layer.addSublayer(trackLayer)
    topView.layer.addSublayer(shapeLayer)

    let color = UIColor(red: 11/255, green: 95/255, blue: 244/255, alpha: 1).cgColor
    let sndColor = UIColor(red: 255/255, green: 87/255, blue: 87/255, alpha: 1).cgColor

    gradient.colors = [color, sndColor]
    gradient.locations = [0.0, 1.0]
    gradient.startPoint = CGPoint(x: 0, y: 0)
    gradient.endPoint = CGPoint(x: 1, y: 0)
    gradient.frame = topView.bounds

    gradient.mask = shapeLayer
    topView.layer.addSublayer(gradient)
}

func animateStroke() {
    if !animated {
        animated = true

        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        var value: Double?

        let distance = currLeasingCar!.currentKm - currLeasing!.startKm
        value = Double(distance) / Double(finalKm)

        basicAnimation.toValue = value
        basicAnimation.duration = 1.5
        basicAnimation.fillMode = .forwards
        basicAnimation.isRemovedOnCompletion = false
        basicAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)

        shapeLayer.add(basicAnimation, forKey: "lineStrokeAnimation")
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 5

问题是您的路径是圆角矩形。在您与我们分享的图像中,\xe2\x80\x99 可能有大约 2-3% 的描边。将其更改为描边 90%,您\xe2\x80\x99 会看到它尝试绘制一个宽且极短的圆角矩形,例如:

\n\n

在此输入图像描述

\n\n

相反,只需将路径设为一条线,它就会按预期工作:

\n\n
let path = UIBezierPath()\n\nlet bounds = topView.bounds\npath.move(to:    CGPoint(x: bounds.minX + bounds.width * 0.15, y: bounds.minY + bounds.height / 1.5))\npath.addLine(to: CGPoint(x: bounds.minX + bounds.width * 0.85, y: bounds.minY + bounds.height / 1.5))\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可能还想将形状图层的盖子变圆:

\n\n
trackLayer.lineCap = .round  // or whatever you want\nshapeLayer.lineCap = .round\n
Run Code Online (Sandbox Code Playgroud)\n\n

当然,这个改变失去了你原来路径的2点高度,所以如果你想让这些形状层\xe2\x80\x99s更厚,只需增加它们各自的高度即可lineWidth值即可。

\n\n
\n\n

一些不相​​关的观察结果:

\n\n
    \n
  • viewDidLayoutSubviews()并且viewDidAppear(_:)应该打电话给他们super实现。

  • \n
  • viewDidLayoutSubviews()可以被多次调用,所以你不想实例化一个新的trackLayer每次都实例化一个新的。或者,如果您这样做,请确保删除前一个。

  • \n
  • 添加子视图/子图层时,谨慎使用bounds而不是frame. 在这种情况下,它可能并不重要,但在某些情况下,您可能会遇到各种奇怪的问题,因为frame在视图\xe2\x80\x99s超级视图\xe2\x80\x99s坐标系中,而bounds是相关视图的坐标系。

  • \n
\n\n
\n\n

就我个人而言,如果您要将这段代码保留在视图控制器中,我\xe2\x80\x99d 建议:

\n\n
    \n
  • 添加形状图层和渐变viewDidLoad
  • \n
  • 更新路径和梯度范围viewDidLayoutSubviews
  • \n
  • I\xe2\x80\x99d 将这些不同的形状层和渐变方法放在自己的私有扩展中。
  • \n
\n\n

更好的是,所有这些动画代码根本不属于 app\xe2\x80\x99s 视图控制器,而是一个UIView子类(或子视图控制器)。

\n\n

因此,也许:

\n\n
@IBDesignable\npublic class GradientProgressView: UIView {\n\n    private var shapeLayer: CAShapeLayer = {\n        let shapeLayer = CAShapeLayer()\n        shapeLayer.strokeColor = UIColor.green.cgColor\n        shapeLayer.fillColor = UIColor.clear.cgColor\n        shapeLayer.lineCap = .round\n        return shapeLayer\n    }()\n\n    private var trackLayer: CAShapeLayer = {\n        let trackLayer = CAShapeLayer()\n        trackLayer.strokeColor = UIColor.groupTableViewBackground.cgColor\n        trackLayer.fillColor = UIColor.clear.cgColor\n        trackLayer.lineCap = .round\n        return trackLayer\n    }()\n\n    private var gradient: CAGradientLayer = {\n        let gradient = CAGradientLayer()\n\n        let color = UIColor(red: 11/255, green: 95/255, blue: 244/255, alpha: 1).cgColor\n        let sndColor = UIColor(red: 255/255, green: 87/255, blue: 87/255, alpha: 1).cgColor\n\n        gradient.colors = [color, sndColor]\n        gradient.locations = [0.0, 1.0]\n        gradient.startPoint = CGPoint(x: 0, y: 0)\n        gradient.endPoint = CGPoint(x: 1, y: 0)\n\n        return gradient\n    }()\n\n    override init(frame: CGRect = .zero) {\n        super.init(frame: frame)\n        addSubLayers()\n    }\n\n    required init?(coder aDecoder: NSCoder) {\n        super.init(coder: aDecoder)\n        addSubLayers()\n    }\n\n    override public func layoutSubviews() {\n        super.layoutSubviews()\n        updatePaths()\n    }\n\n    override public func prepareForInterfaceBuilder() {\n        super.prepareForInterfaceBuilder()\n        setProgress(0.75, animated: false)\n    }\n\n    public func setProgress(_ progress: CGFloat, animated: Bool = true) {\n        if animated {\n            animateStroke(to: progress)\n        } else {\n            shapeLayer.strokeEnd = progress\n        }\n    }\n}\n\nprivate extension GradientProgressView {\n    func addSubLayers() {\n        layer.addSublayer(trackLayer)\n        layer.addSublayer(shapeLayer)\n        layer.addSublayer(gradient)\n    }\n\n    func updatePaths() {\n        let lineWidth = bounds.height / 2\n        trackLayer.lineWidth = lineWidth * 0.75\n        shapeLayer.lineWidth = lineWidth\n\n        let path = UIBezierPath()\n        path.move(to:    CGPoint(x: bounds.minX + lineWidth / 2, y: bounds.midY))\n        path.addLine(to: CGPoint(x: bounds.maxX - lineWidth / 2, y: bounds.midY))\n\n        trackLayer.path = path.cgPath\n        shapeLayer.path = path.cgPath\n\n        gradient.frame = bounds\n        gradient.mask = shapeLayer\n    }\n\n    func animateStroke(to progress: CGFloat) {\n        let key = "lineStrokeAnimation"\n\n        layer.removeAnimation(forKey: key)\n\n        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")\n\n        basicAnimation.toValue = progress\n        basicAnimation.duration = 1.5\n        basicAnimation.fillMode = .forwards\n        basicAnimation.isRemovedOnCompletion = false\n        basicAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)\n\n        shapeLayer.add(basicAnimation, forKey: key)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么视图控制器只是:

\n\n
class ViewController: UIViewController {\n\n    @IBOutlet weak var gradientProgressView: GradientProgressView!\n\n    override func viewDidAppear(_ animated: Bool) {\n        super.viewDidAppear(animated)\n        updateProgress()\n    }\n\n    ...\n}\n\n// MARK: - Progress related methods\n\nprivate extension ViewController {\n    func updateProgress() {\n        let distance = currLeasingCar!.currentKm - currLeasing!.startKm\n        let value = CGFloat(distance) / CGFloat(finalKm)\n        gradientProgressView.setProgress(value)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n