Is it not meant to be possible to control the animations that take place inside of a Form view? I have here a playground that demonstrates the issue, along with a gif of what happens. As you can see, my transition on the 2nd animated view is completely ignored, and I had to manually slow down the video because the durations are ignored, too.
I don't really want a scaling transition, this was just to demonstrate that no matter what I put in there the animation is the same. Is that expected, or is it a bug? Or am I just doing something totally wrong?
It's also not clear to me why the animation of the VStack is handled so differently than the simple Text field, which slides down nicely while the VStack seems to be getting some combination of .move and .opacity.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
@State var showGoodAnimation = false
@State var showBadAnimation = false
var body: some View {
Form {
Toggle(isOn: self.$showGoodAnimation.animation(.easeInOut(duration: 1))) {Text("Yay!")}
if self.showGoodAnimation {
Text("I animate beautifully.")
}
Toggle(isOn: self.$showBadAnimation.animation(.easeInOut(duration: 1))) {Text("Boo!")}
if self.showBadAnimation {
VStack {
Text("Hi.").padding()
Text("I'm a hot mess.").padding()
}
.frame(height: 250)
.transition(.scale)
}
Text("I'm just always here.")
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
At a guess, probably worked around this question some time ago, but for the benefit of those beating their head's against SwiftUI Form and the like now (as I was :-) )
It turns out that Forms, Lists and (no doubt) other components purposely ignore animation customisation because they are "higher-level" SwiftUI View components (unlike V and HStack's).
They do this because SwiftUI's higher-level components are intended to convey semantic information and (more practically) to work well across all platforms. To achieve this, Apple's engineering decision has been to make animation "easy", but (as observed) only to the extent of essentially turning it "on" or "off".
It's likely engineered like this because if a developer wants more control. Then Apple believes encouraging them to use the lower-level components will be less painful than trying to work around the optimisations they have applied to their higher-level view components.
Anyway, for the determined, there is at least one escape hatch via wrapping the View in a container and specifying the .animation(nil)
modifier (as mentioned in Asperi's SO answer here.
An example of this is shown below for completeness; personally, I'm avoiding this pattern as I suspect it's a bit of a footgun.
import PlaygroundSupport
import SwiftUI
struct ContentView: View {
@State var showGoodAnimation = false
@State var showBadAnimation = false
var body: some View {
Form {
Toggle(isOn: self.$showGoodAnimation.animation(.easeInOut(duration: 1))) { Text("Yay!") }
if self.showGoodAnimation {
Text("I animate beautifully.")
}
Toggle(isOn: self.$showBadAnimation.animation()) { Text("Boo!") }
VStack {
if self.showBadAnimation {
List {
Text("I animated differently").padding()
Text("But am I a footgun?").padding()
}
.transition(.asymmetric(insertion: .slide, removal: .opacity))
.animation(.easeOut(duration: 5))
}
}
.animation(nil)
.transition(.slide)
Text("I'm just always here.")
}
}
}
PlaygroundPage.current.setLiveView(ContentView())