The following code displays a number that is increased every second, along with 5 circles that animate between blue and red colors:
struct ContentView: View {
let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
@State private var num = 0
var body: some View {
VStack {
Text("\(num)")
.onReceive(timer) { _ in
num += 1. //commenting this line out allows the CirclesView to keep animating properly.
}
CirclesView()
}
}
}
#Preview {
ContentView()
}
struct CirclesView: View {
let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
@State private var colorOn = false
var body: some View {
ForEach(0...5, id: \.self) { n in
CircleView(size: 30, isOn: n % 2 == 0 ? colorOn : !colorOn)
.onReceive(timer) { _ in
colorOn.toggle()
}
}
}
}
struct CircleView: View {
var size: CGFloat
var isOn: Bool
var body: some View {
Circle()
.fill(isOn ? .red : .blue)
.frame(width: size, height: size)
.animation(.easeInOut(duration: 0.5), value: isOn)
}
}
The problem is (I think) that the ContentView is redrawn every time the timer ticks because num
is a @State var. If I don't update num
in the onReceive modifier, the circles animate properly, but if I do update num
, the Circles keep getting redrawn before they have a chance for their own timer to run.
Is it possible to have the CirclesView not get redrawn? I need its animation to continue to run and not be interrupted when some other state in ContentView changes. Or what is the right way to go about having views that should not be updated due to some continuous animation that should happen? Is the only way to do it to have some entirely different view "own" the CirclesView so that it's not affected?