【问题标题】:Make animated dynamic line chart with UIBezierPath使用 UIBezierPath 制作动画动态折线图
【发布时间】:2021-08-24 15:23:25
【问题描述】:

我有一个使用 UIBezier 路径制作的折线图。这是我的绘图功能的代码

override func draw(_ rect: CGRect) {
    let width = rect.width
    let height = rect.height
    
    let path = UIBezierPath(
        roundedRect: rect,
        byRoundingCorners: .allCorners,
        cornerRadii: Constants.cornerRadiusSize
    )
    path.addClip()
    
    guard let context = UIGraphicsGetCurrentContext() else {
        return
    }
    let colors = [startColor.cgColor, endColor.cgColor]
    
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    
    let colorLocations: [CGFloat] = [0.0, 1.0]
    
    guard let gradient = CGGradient(
        colorsSpace: colorSpace,
        colors: colors as CFArray,
        locations: colorLocations
    ) else {
        return
    }
    
    let startPoint = CGPoint.zero
    let endPoint = CGPoint(x: 0, y: bounds.height)
    context.drawLinearGradient(
        gradient,
        start: startPoint,
        end: endPoint,
        options: []
    )
    
    let margin = Constants.margin
    let graphWidth = width - margin * 2 - 4
    let columnXPoint = { (column: Int) -> CGFloat in
      // Calculate the gap between points
      let spacing = graphWidth / CGFloat(self.graphPoints.count - 1)
      return CGFloat(column) * spacing + margin + 2
    }
    
    let topBorder = Constants.topBorder
    let bottomBorder = Constants.bottomBorder
    let graphHeight = height - topBorder - bottomBorder
    guard let maxValue = graphPoints.max() else {
      return
    }
    let columnYPoint = { (graphPoint: Int) -> CGFloat in
      let yPoint = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
      return graphHeight + topBorder - yPoint // Переворот графика
    }
    
    UIColor.white.setFill()
    UIColor.white.setStroke()
        
    let graphPath = UIBezierPath()

    graphPath.move(to: CGPoint(x: columnXPoint(0), y: columnYPoint(graphPoints[0])))
        
    for i in 1 ..< graphPoints.count {
      let nextPoint = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
        print(nextPoint)
      graphPath.addLine(to: nextPoint)
    }

    self.drawAnimatedGraph(path: graphPath, rect)
    
    context.saveGState()
        
    guard let clippingPath = graphPath.copy() as? UIBezierPath else {
      return
    }
        
    clippingPath.addLine(to: CGPoint(
      x: columnXPoint(graphPoints.count - 1),
      y: height))
    clippingPath.addLine(to: CGPoint(x: columnXPoint(0), y: height))
    clippingPath.close()
    
    clippingPath.addClip()
    
    let highestYPoint = columnYPoint(maxValue)
    let graphStartPoint = CGPoint(x: margin, y: highestYPoint)
    let graphEndPoint = CGPoint(x: margin, y: bounds.height)
            
    context.drawLinearGradient(
      gradient,
      start: graphStartPoint,
      end: graphEndPoint,
      options: [])
    context.restoreGState()
    
    graphPath.lineWidth = 2.0
    graphPath.stroke()

    for i in 0 ..< graphPoints.count {
      var point = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
      point.x -= Constants.circleDiameter / 2
      point.y -= Constants.circleDiameter / 2
          
      let circle = UIBezierPath(
        ovalIn: CGRect(
          origin: point,
          size: CGSize(
            width: Constants.circleDiameter,
            height: Constants.circleDiameter)
        )
      )
      circle.fill()
    }
    
    let linePath = UIBezierPath()

    linePath.move(to: CGPoint(x: margin, y: topBorder))
    linePath.addLine(to: CGPoint(x: width - margin, y: topBorder))

    linePath.move(to: CGPoint(x: margin, y: graphHeight / 2 + topBorder))
    linePath.addLine(to: CGPoint(x: width - margin, y: graphHeight / 2 + topBorder))

    linePath.move(to: CGPoint(x: margin, y: height - bottomBorder))
    linePath.addLine(to: CGPoint(x: width - margin, y: height - bottomBorder))
    let color = UIColor(white: 1.0, alpha: Constants.colorAlpha)
    color.setStroke()
        
    linePath.lineWidth = 1.0
    linePath.stroke()
}

但我的问题是我想用动画绘制图表,我已经找到了它的代码

    func drawAnimatedGraph(path: UIBezierPath, _ rect: CGRect) {
    //Create a CAShape Layer
    let pathLayer: CAShapeLayer = CAShapeLayer()
    pathLayer.frame = self.bounds
    pathLayer.path = path.cgPath
    pathLayer.strokeColor = UIColor.red.cgColor
    pathLayer.fillColor = nil
    pathLayer.lineWidth = 2.0
    pathLayer.lineJoin = CAShapeLayerLineJoin.bevel

    //Add the layer to your view's layer
    self.layer.addSublayer(pathLayer)

    //This is basic animation, quite a few other methods exist to handle animation see the reference site answers
    let pathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
    pathAnimation.duration = 2.0
    pathAnimation.fromValue = 0
    pathAnimation.toValue = 1
    //Animation will happen right away
    pathLayer.add(pathAnimation, forKey: "strokeEnd")
}

问题是我有一个计时器,它删除整数数组的第一个元素并附加随机整数。因此,当我更改数组中的数据并重绘我的图表时,它会在主图表上方绘制新图表。所以我的问题是我是否能够使用这些技术绘制动画折线图,或者我应该寻找另一种解决该问题的方法?

【问题讨论】:

    标签: swift charts


    【解决方案1】:

    如果您看到两个图表,这意味着您正在渲染它两次。两个明显的选择是:

    1. 你可能有形状层的addSublayerdraw(_:)方法。一个人通常会选择其中一个。

      通常,一旦人们开始使用形状层,他们就会放弃旧的 draw(_:) 实现。

    2. 您已多次调用执行addSublayer 的例程。添加形状图层后,不要再调用addSublayer。您可以简单地更新现有CAShapeLayerpath 属性,而不是添加另一个形状层。


    我个人会完全退休draw(_:)(或至少退休图表线部分,至少),在属性中保留对CAShapeLayer的引用,当我想更新图表时,只需更新@987654330 @ 的CAShapeLayer

    为了完整起见,替代方法是坚持使用draw(_:),并通过添加CADisplayLink(为屏幕刷新间隔最佳安排的计时器)执行动画,更新draw(_:)使用的属性,然后调用@ 987654335@ 触发draw(_:) 再次被调用,现在将使用更新的参数。

    全形状层方法可能更容易。

    【讨论】:

    • 我专注于动画代码,甚至没有注意到 OP 覆盖了draw(:_)。手动绘图是 CPU 密集型的,并且往往会创建断断续续的动画。除了更简单之外,使用形状图层和 CAAnimation 的性能也更高。
    【解决方案2】:

    答案是,这很复杂。

    是的,您应该能够使用您展示的动画方法(使用 CAShapeLayer 并使用 strokeEnd 属性的 CABasicAnimation 对其进行动画处理。)

    您当前的 drawAnimatedGraph() 函数在每次调用时都会创建一个新形状图层并将其添加到您的视图中。

    您需要重构该代码以在每次调用时使用相同的形状层,而不是每次都创建和添加一个。

    您还必须处理路径动画只有在起始路径和结束路径具有相同数量的控制点时才能正常工作的事实。您不能在动画制作过程中向图表添加新点。您需要让动画完成,然后添加一个新点并为该新点设置动画。

    为了完成所有这些,您不能只复制/粘贴您在网上找到的动画代码。您需要了解该代码在做什么并将其集成到您的应用程序中。 CAAnimation 很复杂,有点令人困惑,而且没有很好的文档记录,所以学习曲线相当长。

    正如 Rob 在他的回答中指出的那样,您还将基于 draw(:_) 的代码与使用 CAShapeLayer 和 CAAnimation 混合在一起。这很可能会导致问题。我会摆脱 draw(:_) 函数并使用形状层来绘制图形并为其设置动画。

    编辑:

    my answer here

    这是你想要的效果吗?

    【讨论】:

      猜你喜欢
      • 2013-05-23
      • 1970-01-01
      • 1970-01-01
      • 2021-02-25
      • 1970-01-01
      • 1970-01-01
      • 2016-12-02
      • 2022-06-28
      • 2011-07-06
      相关资源
      最近更新 更多