Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ios

swift

swiftui

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

like image 988
aheze Avatar asked Nov 24 '25 07:11

aheze


2 Answers

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

like image 99
aheze Avatar answered Nov 27 '25 00:11

aheze


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)
    }
}
like image 39
Nikolay Ukolov Avatar answered Nov 26 '25 22:11

Nikolay Ukolov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!