4

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.

gohnjanotis
  • 6,513
  • 6
  • 37
  • 57
  • SwiftUI doesn’t do well with chaining animations with the standard modifiers. GeometryEffect is an option. – lorem ipsum Oct 07 '22 at 23:16
  • @loremipsum Do you have any recommended resources or examples for how to achieve this behavior with GeometryEffect? – gohnjanotis Oct 08 '22 at 14:20
  • 1
    Not in particular, if you google "GeometryEffect" and "key frame" you should find some helpful sites. I don't work with animations much but a few complex ones that I have completed mimicked key frames. Basically, just change the behavior manually instead on relying on SwiftUI to do it for you. I've also just made ViewModifiers and used CAAnimation, they are much more efficient. – lorem ipsum Oct 08 '22 at 14:40
  • Changing Form to VStack is working for me (iOS 16.2). "Action Finished Successfully" is displayed. – Marcy Jan 22 '23 at 07:37

0 Answers0