I am making a stock chart graph and I want the very last data point to have a pulsing dot above it. Currently when the view appears, the pulsing dot is lined up with the last data point.
When I scroll through the scroll view and scroll back up the dots appear somewhere else on the screen or nowhere at all. Some still have the dots and others don't, why does this happen when I scroll?
struct AllView: View {
var body: some View {
ScrollView {
VStack {
ForEach(0..<25){ _ in
LineChartView(dataPoints: (0..<100).map { _ in Double.random(in: 0.0...1.0) }, profit: true)
}
}
}
}
}
struct LineChartView: View {
let dataPoints: [Double]
let profit: Bool
var body: some View {
GeometryReader { geometry in
let minY = dataPoints.min() ?? 0
let maxY = dataPoints.max() ?? 1
ZStack {
Path { path in
for (index, dataPoint) in dataPoints.enumerated() {
let x = CGFloat(index) * (geometry.size.width / CGFloat(dataPoints.count - 1))
let y = geometry.size.height - (CGFloat((dataPoint - minY) / (maxY - minY)) * geometry.size.height)
if index == 0 {
path.move(to: CGPoint(x: x, y: y))
} else {
path.addLine(to: CGPoint(x: x, y: y))
}
}
}.stroke(profit ? Color.green : Color.red, lineWidth: 1)
if let last = dataPoints.last {
let x = CGFloat(dataPoints.count - 1) * (geometry.size.width / CGFloat(dataPoints.count - 1))
let y = geometry.size.height - (CGFloat((last - minY) / (maxY - minY)) * geometry.size.height)
PulsingView(size: 50, green: profit).position(x: x, y: y)
}
}
}
}
}
struct PulsingView: View {
@State var animate = false
let size: CGFloat
let green: Bool
var body: some View {
ZStack {
Circle()
.fill(green ? Color.green.opacity(0.65) : Color.red.opacity(0.65))
.frame(width: size, height: size)
.scaleEffect(self.animate ? 1 : 0)
.opacity(animate ? 0 : 1)
Circle()
.fill(green ? Color.green : Color.red)
.frame(width: size / 6, height: size / 6)
}
.onAppear {
self.animate.toggle()
}
.animation(.linear(duration: 1.5).repeatForever(autoreverses: false), value: animate)
}
}
I couldn't reproduce this problem. But I suspect it might be happening because the lines are re-created after they have been scrolled off the screen and then scrolled back. This might mean, .onAppear
is being called again and setting the animate
flag to false. Also, since you are building the lines using random data, this might explain why the pulsing points are in the wrong position when the lines are re-created.
struct AllView: View {
typealias DataPoints = [Double]
let allDataPoints: [DataPoints]
init() {
var allDataPoints = [DataPoints]()
for _ in 0..<25 {
allDataPoints.append(
(0..<100).map { _ in Double.random(in: 0.0...1.0) }
)
}
self.allDataPoints = allDataPoints
}
var body: some View {
ScrollView {
VStack {
ForEach(Array(allDataPoints.enumerated()), id: \.offset) { index, dataPoints in
LineChartView(dataPoints: dataPoints, profit: true)
}
}
}
}
}
animate
flag when the line goes out of view by adding an .onDisappear
callback to PulsingView
:.onDisappear {
animate = false
}