Michał Śniady
Michał Śniady

Reputation: 159

How to animate particular button from ForEach in SwiftUI?

I have a problem with animating buttons. I have them created using ForEach method. The layout of my app consists of 3 flags. We have text that is saying that we have to choose a flag of particular country and if we choose the correct one we gain points.

I want to animate particular button when it is pressed. Doesn't matter if the answer is correct or not.

My code for creating these buttons looks like this:

ForEach(0 ..< 3) { number in
    Button(action: {
        withAnimation {
            if number == self.correctAnswer {
                self.animationAmount += 360
            }
        }
        
        self.flagTapped(number)
    }) {
        ImageView(myImage: self.countries[number])
    }
    .rotation3DEffect(.degrees(number == self.correctAnswer ? self.animationAmount : 0), axis: (x: 0, y: 1, z: 0))
    .opacity(self.showAnimation && number != self.correctAnswer ? 0.25 : 1)
    .background(self.showAnimation && number != self.correctAnswer ? Color.red: nil)
}

self.flagTapped(number) toggles showAnimation to be true.

Upvotes: 3

Views: 1220

Answers (2)

Asperi
Asperi

Reputation: 257493

Here is possible alternate approach - more appropriate looks to move button into separated view and let it animate itself when needed.

Demo prepared on some replicated simplified code (due to absent parts in provided snapshot). Tested with Xcode 12 / iOS 14.

demo

struct DemoView: View {
    let countries = ["flag-1", "flag-2", "flag-3", "flag-4"]

    @State private var number: Int = -1
    let correctAnswer = 1
    var body: some View {
        VStack {
            ForEach(0 ..< 3) { number in
                FlagButton(number: number, image: self.countries[number], answer: correctAnswer){
                    self.flagTapped(number)
                }
            }
        }
    }

    private func flagTapped(_ number: Int) {
        self.number = number
    }
}


struct FlagButton: View {
    let number: Int
    let image: String
    let answer: Int
    var showAnimation = false
    let action: () -> ()

    @State private var animationAmount = Double.zero
    var body: some View {
        Button(action: {
            if self.number == self.answer {
                self.animationAmount += 360
            }
            action()
        }) {
            Image(image)
        }
        .rotation3DEffect(.degrees(number == answer ? self.animationAmount : 0), axis: (x: 0, y: 1, z: 0))
        .opacity(self.showAnimation && number != answer ? 0.25 : 1)
        .background(self.showAnimation && number != answer ? Color.red: nil)
        .animation(.default)
    }
}

Upvotes: 3

Gaurav Ganju
Gaurav Ganju

Reputation: 11

@State private var selectedFlag = -1 //Add this in your ContentView struct
ForEach(0..<3) { number in
                    Button {
                        flagTapped(number)
                        noOfQuestions += 1
                        if noOfQuestions == 8 {
                            showingEnd = true
                        }
                        if scoreTitle == "Correct Answer" {
                            score += 1
                        }
                    } label: {
                        Image(countries[number])
                            .renderingMode(.original)
                            .clipShape(Capsule())
                            .shadow(radius: 5)
                            .rotation3DEffect(.degrees(selectedFlag == number ? 360 : 0),
                                              axis: (x: 0, y:1, z:0))
                            .opacity((selectedFlag == number || selectedFlag == -1) ? 1 : 0.25)
                            .animation(.default, value: selectedFlag )
                            .scaleEffect((selectedFlag == number || selectedFlag == -1) ? 1 : 0.75)
                    }
                }

The selectedFlag var will help determine which of the flags is in currently chosen and applying animations to the specific images rather than the buttons works best.

Upvotes: 1

Related Questions