Search code examples
animationswiftuitransition

How to animate something when WithAnimation cannot be used?


I have a bunch of elements on a VStack which visibility depends on a bool. Like this

if numberOfArrayElementsWithValidNames() != 0 {
  Button1()
  Button2()
  Button3()
}

That array is manipulated by many forms, and the array may contain elements with valid or invalid names. This is why I cannot use withAnimation to animate this.

If the number of valid elements on that array is zero, I want the buttons to disappear; otherwise, if the count is not zero anymore, they should appear. But I want them to appear/disappear with fade in/fade out, not suddenly as they are doing now.

How do I do this?


Solution

  • As already suggested in the comments and another answer, you can make the animation more specific by using an .animation modifier. However, if you use the array as the value for the modifier, you may still get some unwanted animation in other parts of the view, just as you had before.

    I would suggest creating a computed property to evaluate the condition for showing the buttons. Then use this as the value for the .animation modifier.

    Like this:

    struct ContentView: View {
    
        @State private var names: [String] = []
    
        private var arrayHasElementsWithValidNames: Bool {
            return names.firstIndex(of: "Valid") != nil
        }
    
        var body: some View {
            VStack(spacing: 20) {
                Color.yellow.frame(height: 20)
                demoControls
                if arrayHasElementsWithValidNames {
                    Button("Button1") {}
                    Button("Button2") {}
                    Button("Button3") {}
                }
                Color.orange.frame(height: 20)
            }
            .animation(.easeInOut, value: arrayHasElementsWithValidNames)
        }
    
        var demoControls: some View {
            VStack {
                Grid(horizontalSpacing: 30) {
                    GridRow {
                        Text("Array size")
                        Text("Number of valid names")
                    }
                    GridRow {
                        Text("\(names.count)")
                        Text("\(names.filter { $0 == "Valid" }.count)")
                    }
                }
                .drawingGroup()
                Button("Add valid name") { names.append("Valid") }
                Button("Add invalid name") { names.append("Invalid") }
                Button("Remove valid name") {
                    if let index = names.firstIndex(of: "Valid") {
                        names.remove(at: index)
                    }
                }
                Button("Remove all") { names.removeAll() }
            }
            .padding()
            .border(.blue)
            .buttonStyle(.bordered)
        }
    }