Home > Mobile >  SwiftUI Animation causes binding conditional view to flash on and off
SwiftUI Animation causes binding conditional view to flash on and off

Time:11-21

I have an issue with this setup:

ZStack {
  ViewOne
  if something ? ViewTwo : nil
}
.animation()

The problem is that when the animation starts, ViewTwo flashes on and off. I'm thinking it has something to do with the view re-rendering or something? But I can't quite figure it out. I've tried moving the animation around, using it on each view separately, combining it all in one view, but it always flashes when it's based on a conditional. I'd like BOTH views to animate together.

Here is a piece of reproducible code snippet.

struct ContentView: View {
    @State var isAnimating: Bool
    
    var body: some View {
        ZStack {
            VStack {
                ForEach((1...5).reversed(), id: \.self) {_ in
                    ZStack {
                        RoundedRectangle(cornerRadius: 5)
                            .foregroundColor(.blue)
                            .frame(width: 200, height: 50)
                            .rotationEffect(.degrees(isAnimating == true ? 5 : 0))
                        isAnimating
                        ? ButtonImage()
                        : nil
                    }
                    .animation(
                        .easeInOut(duration: 0.3)
                        .repeatForever(autoreverses: true)
                        , value: isAnimating
                    )
                }
                Button(action: {
                    self.isAnimating.toggle()
                }, label: {
                    Text("Animate")
                })
            }
        }
        .rotationEffect(.degrees(isAnimating == true ? 5 : 0))
    }
}

struct ButtonImage: View {
    private let buttonSize: CGSize = CGSize(width: 25, height: 25)
    
    var body: some View {
        Button(action: {
            // to something
        }) {
            Image(systemName: "flame")
                .resizable()
                .renderingMode(.template)
                .background(Color.red)
                .foregroundColor(Color.yellow)
                .frame(width: buttonSize.width, height: buttonSize.height)
        }
        .frame(width: buttonSize.width, height: buttonSize.height)
        .offset(x: -buttonSize.width / 2, y: -buttonSize.height / 2)
}
   

Any ideas of how to resolve this? Showing a view based on a condition, while also animating it without it flashing?

CodePudding user response:

Figured out a way! Not sure if it's the best approach, but it works.

Add the animation to both views separately, then add an opacity modifier to the second view. Here is the code I used.

struct ContentView: View {
    @State var isAnimating: Bool
    
    var body: some View {
        ZStack {
            VStack {
                ForEach((1...5).reversed(), id: \.self) {_ in
                    ZStack {
                        RoundedRectangle(cornerRadius: 5)
                            .foregroundColor(.blue)
                            .frame(width: 200, height: 50)
                            .rotationEffect(.degrees(isAnimating == true ? 5 : 0))
                            .animation(
                                .easeInOut(duration: 0.3)
                                .repeatForever(autoreverses: true)
                                , value: isAnimating
                            )
                        ButtonImage(isAnimating: $isAnimating)
                            .opacity(isAnimating ? 1 : 0)
                    }
                }
                Button(action: {
                    self.isAnimating.toggle()
                }, label: {
                    Text("Animate")
                })
            }
        }
        .rotationEffect(.degrees(isAnimating == true ? 5 : 0))
    }
}

struct ButtonImage: View {
    private let buttonSize: CGSize = CGSize(width: 25, height: 25)
    @Binding var isAnimating: Bool
    
    var body: some View {
        Button(action: {
            // to something
        }) {
            Image(systemName: "flame")
                .resizable()
                .renderingMode(.template)
                .background(Color.red)
                .foregroundColor(Color.yellow)
                .frame(width: buttonSize.width, height: buttonSize.height)
        }
        .frame(width: buttonSize.width, height: buttonSize.height)
        .offset(x: -buttonSize.width / 2, y: -buttonSize.height / 2)
        .rotationEffect(.degrees(isAnimating == true ? 5 : 0))
        .animation(
            .easeInOut(duration: 0.3)
            .repeatForever(autoreverses: true)
            , value: isAnimating
        )
    }
}
  • Related