Reputation: 6680
I'd like to make this bar's height (part of a larger bar graph) animate on appear from 0 to its given height, but I can't quite figure it out in SwiftUI? I found this code that was helpful, but with this only the first bar (of 7) animates:
struct BarChartItem: View {
var value: Int
var dayText: String
var topOfSCaleColorIsRed: Bool
@State var growMore: CGFloat = 0
@State var showMore: Double = 0
var body: some View {
VStack {
Text("\(value)%")
.font(Font.footnote.monospacedDigit())
RoundedRectangle(cornerRadius: 5.0)
.fill(getBarColor())
.frame(width: 40, height: growMore)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation(.linear(duration: 7.0)) {
growMore = CGFloat(value)
showMore = 1
}
}
}.opacity(showMore)
Text(dayText.uppercased())
.font(.caption)
}
}
And this is the chart:
HStack(alignment: .bottom) {
ForEach(pastRecoveryScoreObjects) { pastRecoveryScoreObject in
BarChartItem(value: pastRecoveryScoreObject.recoveryScore, dayText: "\(pastRecoveryScoreObject.date.weekdayName)", topOfSCaleColorIsRed: false)
}
}
Upvotes: 4
Views: 2606
Reputation: 783
This works: you can get the index of the ForEach loop and delay each bar's animation by some time * that index. All the bars are connected to the same state variable via a binding, and the state is set immediately on appear. You can play with the parameters to tune it to your own preferences.
struct TestChartAnimationView: View {
let values = [200, 120, 120, 90, 10, 80]
@State var open = false
var animationTime: Double = 0.25
var body: some View {
VStack {
Spacer()
HStack(alignment: .bottom) {
ForEach(values.indices, id: \.self) { index in
BarChartItem(open: $open, value: values[index])
.animation(
Animation.linear(duration: animationTime).delay(animationTime * Double(index))
)
}
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
open = true
}
}
}
}
struct BarChartItem: View {
@Binding var open: Bool
var value: Int
var body: some View {
VStack {
Text("\(value)%")
.font(Font.footnote.monospacedDigit())
RoundedRectangle(cornerRadius: 5.0)
.fill(Color.blue)
.frame(width: 20, height: open ? CGFloat(value) : 0)
Text("Week".uppercased())
.font(.caption)
}
}
}
Upvotes: 2
Reputation: 30278
Edit: ForEach animations are kind of weird. I'm not sure why, but this works:
struct ContentView: View {
let values = [200, 120, 120, 90, 10, 80]
var body: some View {
HStack(alignment: .bottom) {
ForEach(values, id: \.self) { value in
BarChartItem(totalValue: value)
.animation(.linear(duration: 3))
}
}
}
}
struct BarChartItem: View {
@State var value: Int = 0
var totalValue = 0
var body: some View {
VStack {
Text("\(value)%")
.font(Font.footnote.monospacedDigit())
RoundedRectangle(cornerRadius: 5.0)
.fill(Color.blue)
.frame(width: 20, height: CGFloat(value))
Text("Week".uppercased())
.font(.caption)
}.onAppear {
value = totalValue
}
}
}
Instead of applying the animation in the onAppear
, I applied an implicit animation inside the ForEach
.
Result:
Upvotes: 1