Using SwiftUI Views with animations works great with @State variable which modifications causes declared animations. But soon as I wrap the view inside hosting Controller animation stops.
Current example:
I have a list that should present custom View cells. Every cell have @State variable visibleSide
and when you switch sides you have rotating animations. After wrapping it inside HostingViewController changing state just switches sides of cell without any animation.
But I’ve figured out If I use ObservableObject which I pass to cells as ViewModel instead of @State animations are still there. But as Im using mentioned viewModel for whole list every cell animates after changing of visibleSides variable.
Question: Do I need observableObject for each cell or is there other way to update single cell and presist only that cell animation
Code example:
struct TrainingCard: View {
let item: TrainingCardDetailsItem
@State private var visibleSide = FlipViewSide.front
var body: some View {
FlipView(visibleSide: visibleSide) {
TrainingCardGraphic(item: item, style: .cardFront,
flipAction: flipCard)
} back: {
TrainingCardGraphic(item: item, style: .cardBack,
flipAction: flipCard)
}
.contentShape(Rectangle())
.animation(.flipCard, value: visibleSide)
}
func flipCard() {
visibleSide.toggle()
}
}
struct FlipView<Front: View, Back: View>: View {
var visibleSide: FlipViewSide
@ViewBuilder var front: Front
@ViewBuilder var back: Back
var body: some View {
ZStack {
front
.modifier(FlipModifier(side: .front, visibleSide: visibleSide))
back
.modifier(FlipModifier(side: .back, visibleSide: visibleSide))
}
}
}
enum FlipViewSide {
case front
case back
mutating func toggle() {
self = self == .front ? .back : .front
}
}
struct FlipModifier: AnimatableModifier {
var side: FlipViewSide
var flipProgress: Double
init(side: FlipViewSide, visibleSide: FlipViewSide) {
self.side = side
self.flipProgress = visibleSide == .front ? 0 : 1
}
var visible: Bool {
switch side {
case .front:
return flipProgress <= 0.5
case .back:
return flipProgress > 0.5
}
}
public func body(content: Content) -> some View {
ZStack {
content
.opacity(visible ? 1 : 0)
.accessibility(hidden: !visible)
}
.scaleEffect(x: scale, y: 1.0)
.rotation3DEffect(.degrees(flipProgress * -180), axis: (x: 0.0, y: 1.0, z: 0.0), perspective: 0.5)
}
var scale: CGFloat {
switch side {
case .front:
return 1.0
case .back:
return -1.0
}
}
}