Home > Blockchain >  SwiftUI custom animation does not apply on inner view change
SwiftUI custom animation does not apply on inner view change

Time:09-07

I have a custom bounce animation applied to a view with multiple inner views which can change depending on conditions. My issue is that when the inner view changes, the animation applied to a parent no longer applies to it.

Example:

enter image description here

Sample Code:

struct ContentView: View {
    
    @State var number = 1
    
    var body: some View {
        VStack {
            ZStack {
                Circle()
                    .foregroundColor(.gray)
                    .frame(width: 100, height: 100, alignment: .center)
                if number == 1 {
                    Image(systemName: "person")
                }
                else if number == 2 {
                    Image(systemName: "globe")
                }
                else {
                    Image(systemName: "square")
                }
            }
            .bounceEffect()
            Button {
                if number != 3 {
                    number  = 1
                }
                else {
                    number = 1
                }
            } label: {
                Text("CHANGE")
            }
            .padding()
        }
        .padding()
    }
}

struct BounceEffect: ViewModifier {
    
    @State var bounce = false
    
    var allow: Bool
    
    func body(content: Content) -> some View {
        content
            .offset(y: (bounce && allow) ? -5 : 0)
            .animation(.interpolatingSpring(mass: 1, stiffness: 350, damping: 5, initialVelocity: 10).repeatForever(autoreverses: false).delay(1), value: UUID())
            .onAppear {
                if allow {
                    bounce.toggle()
                }
            }
    }
}

extension View {
    func bounceEffect(allow: Bool = true) -> some View {
        modifier(BounceEffect(allow: allow))
    }
}

Thank you.

CodePudding user response:

The reason of the bounceEffect don't get the button after you change because you are creating the new image when ever you tap the button change.

The action you need here: just only change the system image of the image not creating new one every time the button change is tapped

The code will be like this

struct ContentView: View {
    @State var number = 1
    @State var imageName = "person"

        var body: some View {
            VStack {
                ZStack {
                    Circle()
                        .foregroundColor(.gray)
                        .frame(width: 100, height: 100, alignment: .center)
                    Image(systemName: imageName)
                }
                .bounceEffect()
                Button {
                    if number != 3 {
                        number  = 1
                    }
                    else {
                        number = 1
                    }

                    if number == 1 {
                        imageName = "person"
                    }
                    else if number == 2 {
                        imageName = "globe"
                    }
                    else {
                        imageName = "square"
                    }
                } label: {
                    Text("CHANGE")
                }
                .padding()
            }
            .padding()
        }
}

The result The result

CodePudding user response:

When the new images appear, they aren’t the ones involved in the animation. One way to fix this is to include all 3 images from the start, and just cycle their visibility by changing opacity:

ZStack {
    Circle()
        .foregroundColor(.gray)
        .frame(width: 100, height: 100, alignment: .center)
        
    Image(systemName: "person")
        .opacity(number == 1 ? 1 : 0)

    Image(systemName: "globe")
        .opacity(number == 2 ? 1 : 0)

    Image(systemName: "square")
        .opacity(number == 3 ? 1 : 0)
}

@bewithyou’s answer is more scalable and should be the accepted answer. Here is a better way to structure the selection of your images:

struct ContentView: View {
    @State var number = 0
    let names = ["person", "globe", "square", "house", "car"]
    
    var body: some View {
        VStack {
            ZStack {
                Circle()
                    .foregroundColor(.gray)
                    .frame(width: 100, height: 100, alignment: .center)
                Image(systemName: names[number])
            }
            .bounceEffect()
            Button {
                number = (number   1) % names.count
            } label: {
                Text("CHANGE")
            }
            .padding()
        }
        .padding()
    }
}
  • Related