swiftchartsswift6ios18xcode16

How to make the LinePlot feature of Swift Charts dynamic and animated?


I'm trying to get the LinePlot feature from Xcode 16 to work. I tried the example code and it works, but I'm struggling to make it work interactively and animated.


Solution

  • The answer has been taken from this blogpost about creating a spiral with LinePlot and animating it:

    https://lucasvandongen.dev/recreating_ovo_timer_in_swiftui.php

    The result looks something like this:

    An example of a spiral drawn with LinePlot and two sliders to move the begin- and endpoints


    import Charts
    import SwiftUI
    
    struct PlotPoint: Identifiable {
        let id: Int
        let x, y: Double
    }
    
    public struct Counter: View {
        private static let minimumAngle: CGFloat = 60
        private static let maximumAngle: CGFloat = 75
    
        @State private var beginAngle: CGFloat = Self.minimumAngle
        @State private var endAngle: CGFloat = Self.maximumAngle
        @State private var points: [PlotPoint] = [
            PlotPoint(
                id: 1,
                x: 0,
                y: 0
            ),
            PlotPoint(
                id: 2,
                x: 1,
                y: 3
            )
        ]
    
        public init() { }
    
        public var body: some View {
            Chart(points) {
                LinePlot(
                    x: "x",
                    y: "y",
                    t: "t",
                    domain: beginAngle...endAngle
                ) { t in
                    spiral(t: t)
                }
    
                PointMark(
                    x: .value("Wing Length", $0.x),
                    y: .value("Wing Width", $0.y)
                ).symbol {
                    Circle()
                        .fill(.white)
                        .stroke(
                            .blue,
                            lineWidth: 2
                        )
                        .frame(
                            width: 8,
                            height: 8
                        )
                }
            }
            .chartXScale(domain: -8...8)
            .chartYScale(domain: -8...8)
            .chartXAxis(.hidden)
            .chartYAxis(.hidden)
            .aspectRatio(
                1,
                contentMode: .fit
            )
    
            Slider(
                value: $beginAngle,
                in: Self.minimumAngle...endAngle
            )
            .onChange(
                of: beginAngle,
                perform: updateBeginPoint(to:)
            )
            .onAppear {
                updateBeginPoint(to: beginAngle)
            }
    
            Slider(
                value: $endAngle,
                in: beginAngle...Self.maximumAngle
            )
            .onChange(
                of: endAngle,
                perform: updateEndPoint(to:)
            )
            .onAppear {
                updateEndPoint(to: endAngle)
            }
        }
    
        private func updateBeginPoint(to newValue: Double) {
            let endPoint = spiral(t: newValue)
    
            points[0] = PlotPoint(
                id: 2,
                x: endPoint.0,
                y: endPoint.1
            )
        }
    
        private func updateEndPoint(to newValue: Double) {
            let endPoint = spiral(t: newValue)
    
            points[1] = PlotPoint(
                id: 2,
                x: endPoint.0,
                y: endPoint.1
            )
        }
    
        private func spiral(t: Double) -> (Double, Double) {
            let a: CGFloat = 0.1
            let b: CGFloat = 0.1
            let r = a + b * t
            let x = r * cos(t)
            let y = r * sin(t)
    
            return (x, y)
        }
    }