Home > Back-end >  SwiftUI animation only works with `withAnimation` block and not with `.animation`
SwiftUI animation only works with `withAnimation` block and not with `.animation`

Time:05-25

I wanted to make a little view that slides in with an error message:

enum SomeStates {
    case good
    case bad
    var errorMessage: String? {
        switch self {
        case .good: return nil
        case .bad: return "a sad error has occured"
        }
    }
}

struct ErrorForState: View {
    @State var errorMessage: String?
    @Binding var state: SomeStates
    var body: some View {
        return Group {
            if let message = errorMessage {
                Text(message)
                    .foregroundColor(Color(hex: 0x874b4b))
                    .padding(15)
                    .background(Color(hex: 0xffefef))
                    .cornerRadius(10)
                    .transition(.scale)
            }
        }
        .onChange(of: state) { _ in
            withAnimation(.spring()) {
                errorMessage = state.errorMessage
            }
        }
    }
}

And this works! My view detects when the binding changes and sets errorMessage on itself in an withAnimation block.

But I don't like using withAnimation here because it's quite verbose, requires me to have an extra state variable errorMessage and I'm not a fan of how it decouples details about the animation /from/ the thing being animated and /to/ the thing triggering the animation. I originally wanted to do something like this:

struct ErrorForState: View {
    @Binding var state: SomeStates
    var body: some View {
        
        return Group {
            if let message = state.errorMessage {
                Text(message)
                    .foregroundColor(Color(hex: 0x874b4b))
                    .padding(15)
                    .background(Color(hex: 0xffefef))
                    .cornerRadius(10)
                    .transition(.scale)
                    .animation(Animation.easeInOut(duration: 1.0), value: state.errorMessage)
            }
        }
    }
}

however this does not work, and instead appears/disappears instantly. In fact, every solution attempt I've made that makes use of .animation on iOS 15 does not seem to work.

I suspect I'm not understanding how .animation works - maybe I have it in the wrong spot above (maybe it's because my Text view is rendered conditionally?). It also doesn't seem to work when attached to the Group either.

CodePudding user response:

Here is an approach fro you:

Using Animation inside if or if let does not give you any benefit or result! because animation works with value changes, when you put it inside if, it got just initialized, do not use group unless you are sure you can replace it with group, use VStack instead.

struct ErrorForState: View {
    @Binding var state: SomeStates
    var body: some View {
        
        return VStack(spacing: .zero) {
            if let message = state.errorMessage {
                Text(message)
                    .foregroundColor(Color(hex: 0x874b4b))
                    .padding(15)
                    .background(Color(hex: 0xffefef))
                    .cornerRadius(10)
                    .transition(.scale)   
            }
        }
        .animation(Animation.easeInOut(duration: 1.0), value: state.errorMessage)
    }
}

CodePudding user response:

Here is an approach fro you:

Using Animation inside if or if let does not give you any benefit or result! because animation works with value changes, when you put it inside if, it got just initialized, do not use group unless you are sure you can replace it with group, use VStack instead.

struct ErrorForState: View {
    @Binding var state: SomeStates
    var body: some View {
        
        return VStack(spacing: .zero) {
            if let message = state.errorMessage {
                Text(message)
                    .foregroundColor(Color(hex: 0x874b4b))
                    .padding(15)
                    .background(Color(hex: 0xffefef))
                    .cornerRadius(10)
                    .transition(.scale)   
            }
        }
        .animation(Animation.easeInOut(duration: 1.0), value: state.errorMessage)
    }
}
  • Related