I couldn't manage to migrate a simple animation with the withAnimation(_:_:)
function from this:
struct WelcomeView: View {
let greetingTexts: [String]
@ViewBuilder
var body: some View {
VStack {
ForEach(greetingTexts.indices, id: \.self) { index in
Text(greetingTexts[index])
.font(.mtgSans(.title1))
.multilineTextAlignment(.center)
.transition(.opacity)
.animation(Animation.easeOut.speed(0.5).delay(0.08 * Double(index)))
}
}
}
}
To this:
struct WelcomeView: View {
let greetingTexts: [String]
@ViewBuilder
var body: some View {
VStack {
ForEach(greetingTexts.indices, id: \.self) { index in
withAnimation(.easeOut.speed(0.5).delay(0.08 * Double(index))) {
Text(greetingTexts[index])
.font(.mtgSans(.title1))
.multilineTextAlignment(.center)
.transition(.opacity)
}
}
}
}
}
And it doesn't animate at all. Any clue?
Animations need to be associated with a value that changes after the view is first shown, such as a change to a state variable. The examples you posted do not change after initial show, so there is no animation.
The animation you linked to shows two lines of text that simply move up by a small amount. Both lines appear to move together. However, your examples are using a delay that is dependent on the index position, so I guess you are after a staggered animation.
A simple boolean value can be used for triggering the animation. Some notes and suggestions:
The animation is triggered when the state variable changes, this can be done in .onAppear
.
withAnimation
can be used to perform the change. Alternatively, an .animation
modifier can be used, which specifies the value being observed. Both techniques are valid. However, when the value is being updated in more than one place, it may be more convenient to have one .animation
modifier, which serves all updates, instead of having to perform all updates withAnimation
.
Instead of using an index value to access the array items, use an array enumeration.
The body
of a View
does not need to be annotated with @ViewBuilder
, this is implicit (since iOS 14).
In the updated example below there is also a button, for repeating the animation.
struct WelcomeView: View {
let greetingTexts: [String] =
["The quick brown fox", "jumps over the lazy dog"]
@State private var isShowing = false
var body: some View {
VStack {
ForEach(Array(greetingTexts.enumerated()), id: \.offset) { index, text in
Text(text)
.font(.mtgSans(.title1))
.multilineTextAlignment(.center)
.opacity(isShowing ? 1 : 0)
.offset(y: isShowing ? 0 : 30)
.animation(
.easeOut.delay(isShowing ? 0.5 * Double(index + 1) : 0),
value: isShowing
)
}
Button("Toggle") { isShowing.toggle() }
.buttonStyle(.bordered)
.padding(.top, 100)
}
.onAppear { isShowing = true }
}
}