aheze
aheze

Reputation: 30336

SwiftUI - animation determined by multiple properties doesn't animate, or updates weirdly

I made an extremely fun game where you toggle switches on and off. If all 3 are on, then the Rectangle should flash green. Here's my code:

struct ContentView: View {
    @State var toggle1IsOn = false
    @State var toggle2IsOn = false
    @State var toggle3IsOn = false

    var body: some View {
        VStack {
            Toggle("Toggle 1", isOn: $toggle1IsOn)
            Toggle("Toggle 2", isOn: $toggle2IsOn)
            Toggle("Toggle 3", isOn: $toggle3IsOn)

            Rectangle()
                .fill(
                    toggle1IsOn && toggle2IsOn && toggle3IsOn
                        ? Color.green /// if all 3 toggles turned on, then become green
                        : Color.gray
                )
                .animation(
                    toggle1IsOn && toggle2IsOn && toggle3IsOn
                        ? .default.repeatForever(autoreverses: true)
                        : .default, /// remove forever animation if not all 3 toggles are on
                    value: toggle1IsOn
                )
        }
    }
}

The color changes to green, but sometimes the repeat-forever animation doesn't run. Also, sometimes I get a weird purple color that I didn't define anywhere in my code. What is happening?

Toggling switches, once all 3 are on, rectangle becomes green

Upvotes: 3

Views: 1531

Answers (2)

Nikolay Ukolov
Nikolay Ukolov

Reputation: 107

It's not good idea to use ternary operator for animation. Try to separate green rectangle and flash rectangle

struct ContentView: View {
    @State var toggle1IsOn = false
    @State var toggle2IsOn = false
    @State var toggle3IsOn = false
    
    @State private var flashRectangleDidAppear = false

    var body: some View {
        VStack {
            Toggle("Toggle 1", isOn: $toggle1IsOn)
            Toggle("Toggle 2", isOn: $toggle2IsOn)
            Toggle("Toggle 3", isOn: $toggle3IsOn)

            if toggle1IsOn && toggle2IsOn && toggle3IsOn {
                flashRectangle
            } else {
                grayRectangle
            }
        }
        
    }
    
    var flashRectangle: some View {
        Rectangle()
            .fill(flashRectangleDidAppear ? Color.green : Color.gray)
            .animation(.default.repeatForever(autoreverses: true), value: flashRectangleDidAppear)
            .onAppear {
                flashRectangleDidAppear.toggle()
            }
    }
    
    var grayRectangle: some View {
        Rectangle()
            .fill(Color.gray)
    }
}

Upvotes: 1

aheze
aheze

Reputation: 30336

The problem is in the value of animation(_:value:). From the documentation:

A value to monitor for changes.

You're only passing in the first property toggle1IsOn, so when the other ones are changed, the animation doesn't react. Instead, you should pass in the condition that causes the green to be shown:

value: toggle1IsOn && toggle2IsOn && toggle3IsOn

As long as value conforms to Equatable it's fine. You can also move it into a separate property, shouldFlash, so you don't repeat code.

struct ContentView: View {
    @State var toggle1IsOn = false
    @State var toggle2IsOn = false
    @State var toggle3IsOn = false

    var body: some View {
        
        /// accumulate into 1 constant
        let shouldFlash = toggle1IsOn && toggle2IsOn && toggle3IsOn
        
        VStack {
            Toggle("Toggle 1", isOn: $toggle1IsOn)
            Toggle("Toggle 2", isOn: $toggle2IsOn)
            Toggle("Toggle 3", isOn: $toggle3IsOn)

            Rectangle()
                .fill(
                    shouldFlash
                        ? Color.green
                        : Color.gray
                )
                .animation(
                    shouldFlash
                        ? .default.repeatForever(autoreverses: true)
                        : .default,
                    value: shouldFlash /// here!
                )
        }
    }
}

Result:

Animation runs without issues

Upvotes: 2

Related Questions