UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 34 Challenge : Tap on a wrong flag animation

Forums > 100 Days of SwiftUI

I've been stuck for several days on this challenge, I've been racking my brain and finally succeeded in the first 2 challenges but on the 3rd one I'm running out of ideas!

I think my approach is not the right one with the ternary conditions on my modifiers, it passed for the first animations, but having to manage the animations for the wrong answers, I think I have to change my approach... without giving me the final answer, would you have a runway to show me?

Here is my code, thank you !

import SwiftUI

struct FlagImage: View {
    var countryName: String

    var body: some View {
        Image(countryName)
            .renderingMode(.original)
            .cornerRadius(10)
            .shadow(color: .black, radius: 2)
    }
}

struct ContentView: View {
    @State private var showingScore = false
    @State private var userScore = 0
    @State private var animate = false

    @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()

    @State private var correctAnswer = Int.random(in: 0...2)

    func flagTapped(_ number: Int) {
        if number == correctAnswer {
            userScore += 1
        }
        askQuestion()
    }

    func askQuestion() {
        animate = false
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
    }

    var body: some View {

        ZStack {
            Color("TaiyouColor").edgesIgnoringSafeArea(.all)
            VStack(spacing: 30) {
                VStack {
                    Text("Tap the flag of")
                        .foregroundColor(.white)
                    Text(countries[correctAnswer])
                        .foregroundColor(.white)
                        .font(.largeTitle)
                        .fontWeight(.black)
                }
                Spacer()
                ForEach(0 ..< 3) { number in
                    Button(action: {
                        withAnimation(.easeOut(duration: 0.5)) {
                            self.animate = true
                        }
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                            self.flagTapped(number)
                        }

                    }) {
                            FlagImage(countryName: self.countries[number])
                    }
                    .rotation3DEffect(.degrees(number == self.correctAnswer && self.animate ? 180 : 0.0), axis: (x: 0, y: 1, z: 0))
                    .opacity(number != self.correctAnswer && self.animate ? 0.25 : 1)

                }
                Spacer()
                Text("Score: \(userScore)")
                    .foregroundColor(.white)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

3      

I felt weird to do the ternary as well but I've asked people on the discord server and told me that it's a pretty common thing to do apparently. 🤷🏻‍♂️

3      

Hi,

I am completing this challenge myself at the moment. I’ll share my approach for animating the wrong answer.

If you just wanted to show something like a red overlay over both wrong answers, I believe you could still use a solution based on conditional modifiers with the ternary operator. In that case, I’d split the animate state var into animateCorrectAnswer and animateWrongAnswer and check if the user tapped the correct answer in the first part of the button action.

The modifiers you posted do the job in case the user selects the correct answer, so I would just replace animate with animateCorrectAnswer in those logical conjunctions. Then, I would add an overlay to the buttons, set red as the fill color and finally apply a conditional opacity modifier to the fill color of the overlay. The condition would be something like

number != self.correctAnswer && animateWrongAnswer ? 1 : 0

However, I personally prefer to only set the overlay for the answer the user selected so the game doesn’t give away the right answer. For this, I kept the code similar to yours and added a state vector wrongAnswerOpacity with the opacities for each possible wrong answer. Then, you can use the button action to set the opacity accordingly and set it back to 0 inside flagTapped. I posted my ForEach bellow. Please note that my FlagView uses Capsule as clipShape.

ForEach(0 ..< 3) { flagSelection in
    Button(action: {
        if flagSelection == self.correctAnswer {
            withAnimation(.easeOut(duration: 1)) {
                self.animate = true
            }
        } else {
            withAnimation(.easeOut(duration: 0.25)) {
                self.wrongAnswerOpacity[flagSelection] = 1
            }
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.flagTapped(flagSelection)
        }
    }) {
        FlagView(self.countries[flagSelection])
    }
    .rotation3DEffect(
        flagSelection == self.correctAnswer && self.animate ? .degrees(180) : .degrees(0),  
        axis: (x:0 , y: 1, z: 0)
    )
    .opacity(flagSelection != self.correctAnswer && self.animate ? 0.25 : 1)
    .overlay(Capsule()
        .fill(Color.red)
            .opacity(self.wrongAnswerOpacity[flagSelection]) 
    )
}

4      

What I did was create a variable for the number of the flag tapped. So the condition for the wrong answer effect would check if the answer is different than the correctAnswer and if the tapped flag corresponds to number.

If you don't include that, the two flags that are different from the correct answer will have the modifier applied. Is that what's happening now?

4      

Thanks for the help everyone. I've been trying hard to understand the different ways of solving this challenge.

My question - anyone understand how to stop the animation after clicking the dismiss button / askQuestion() is performed? When I dismiss the alert, the wrong answer is still animated from the withAnimation(.interpolatingSpring(stiffness: 5, damping: 1))

Here's my ForEach code. Thanks to anyone who takes a look!

 ForEach(0 ..< 3) { flagSelection in
                    Button(action: {
                        // if correct or wrong
                        if flagSelection == self.correctAnswer {
                            withAnimation(.easeOut(duration: 1)) {
                                self.animateCorrectAnswer = true
                            }
                        } else {
                            withAnimation(.interpolatingSpring(stiffness: 5, damping: 1)) {
                                self.animateWrongAnswer = true
                                self.wrongAnswer = flagSelection
                            }
                        }

                        self.flagTapped(flagSelection)

                    }) {
                    FlagImage(flag: self.countries[flagSelection])
                    }
// this is where modifiers go
                    .rotation3DEffect(
                        flagSelection == self.correctAnswer && self.animateCorrectAnswer ? .degrees(360) : .degrees(0),
                        axis: (x:0, y: 1, z: 0)
                    )
                    .overlay(flagSelection == self.wrongAnswer && self.animateWrongAnswer ? Capsule().fill(Color.red) : nil)
                    .scaleEffect(flagSelection == self.wrongAnswer && self.animateWrongAnswer ? 0.2 : 1)
                }

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.