I have a SwiftUI app and I want to give visual feedback to the user when an operation completes.
One way I've been trying to do that is by briefly animating in a message, then animating it back out. However, it doesn't seem to be working.
I have used SwiftUI's withAnimation
successfully in the past, and I'm trying something almost identical to this answer.
To isolate the issue, I created a new iOS app that uses SwiftUI in Xcode. I edited the ContentView file to this:
struct ContentView: View {
@State var showSuccessful: Bool = false
var body: some View {
NavigationView {
Form {
if showSuccessful {
Text("Action finished successfully")
.transition(.opacity)
}
Button {
// Do the action...
withAnimation(.easeIn(duration: 2.0)) {
showSuccessful = true
}
withAnimation(.easeIn(duration: 2.0).delay(4.0)) {
showSuccessful = false
}
} label: {
Text("Do Something")
}
}
}
}
}
I'm using long animation times and delays in this example to exaggerate the behavior while I troubleshoot.
When the user taps the button, I would expect the app should show (with a two-second animation) the "Action finished successfully" message, then after four seconds, I would expect that same text to fade out for two seconds.
Instead, when I tap the button, nothing appears to happen.
When I remove the second withAnimation
block, the "Action finished successfully" message fades in, but in one second rather than the two second duration I would expect.
Also, if I specify a .delay
on the first withAnimation
block's animation and with no second withAnimation
block, the delay does not happen.
When I use the same code, except in a VStack
instead of a Form
inside a NavigationView
, I at least get some weird animation behavior, but the "Action finished successfully" text doesn't show up.
Could this be a bug? Or is there something I'm missing about the behavior of withAnimation
in this scenario?
I was able to at least get a delay working by replacing the two withAnimation
blocks with the following:
Task {
withAnimation(Animation.easeIn) {
showSuccessful = true
}
try? await Task.sleep(nanoseconds: 4 * 1_000_000_000)
withAnimation(Animation.easeIn) {
showSuccessful = false
}
}
Even though this workaround achieves the delay, I would like to understand how I can correctly utilize .delay
as part of withAnimation
without using Task.sleep
, and specify animation durations.