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

Day 34 Project 6 Guess the Flag Challenge: Animations (Stuck for 18 hours)...help?

Forums > 100 Days of SwiftUI

Howdy,

We have been stuck on getting our animation to animate when/where we want & have spent approximately 18 hours trying to complete this challenge. We know we have dedicated this much time to this as we stream on twitch & have all of the video of our struggle.

First part of our code where we believe the problem is:

This method is checking to see if we pressed the correct button to win & my thought process was/is this should trigger our animation.

func spinToWin(_ number: Int) -> Bool {
        return number == correctAnswer
    }

This is our code for creating our buttons.

                ForEach(0..<3) { number in
                    Button(action: {
          // flagTapped is merely checking to see if we tapped the correct answer
                        flagTapped(number)
                        withAnimation() {
                            buttonNumber = spinToWin(number) ? number : 0
                            print(buttonNumber)
                        }
                    }) {
                        Image(countries[number])
                            .renderingMode(.original)
                            .flags()
                            .shadow(color: .black, radius: 5)
                            .rotation3DEffect(
                                .degrees(Double(spinToWin(number) ?  360 : 0)),
                                axis: (x: 0, y: 1, z: 0))
                            .animation(.default)
                    }
                }

What is happeing is our Animation (spinning the correct button/image) is not happening until we call our askQuestion method (below) and then it spins the flag in the New Round (with the new flags) & also the button/flag of the new correct answer...hope that makes sense.

func askQuestion() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
        print("New Game")
        buttonNumber = 4
    }

I believe the above code is where this is happening or rather NOT happeing, however I have provided the entire code below if you are so inclined.

In any case, thank you for any guideance you might see your way fit to give.

Baron

https://www.twitch.tv/baron_blackbird

//  ContentView.swift
//  GuessTheFlag
//
//  Created by Baron Blackbird on 9/29/20.
//

import SwiftUI

// formatting how our Flag Buttons should look

struct FlagRender: ViewModifier {
    func body(content: Content) -> some View{
        content
            .clipShape(Capsule())
            .overlay(Capsule().stroke(Color.black, lineWidth: 1))
    }
}
extension View {
    func flags() -> some View {
        self.modifier(FlagRender())
    }
}
struct ContentView: View {
    @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Monaco", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()
    @State private var correctAnswer = Int.random(in: 0...2)
    @State private var showingScore = false
    @State private var scoreTitle = ""
    @State private var score = 0
    @State private var alertMessage = ""
    @State private var testRotation = false
    @State private var buttonNumber: Int = 4
    @State private var spinAmount = 0.0

  func spinToWin(_ number: Int) -> Bool {
      return number == correctAnswer
  }

  var body: some View {
      ZStack {
          LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .top, endPoint: .bottom)
              .ignoresSafeArea(.all)
          VStack {
              VStack {

                  Text("Choose the correct flag for")
                      .foregroundColor(.white)
                      .font(.title)

                  Text(countries[correctAnswer])
                      .foregroundColor(.white)
                      .font(.largeTitle)
                      .fontWeight(.bold)

   // Here we create our 3 button flags               
                  ForEach(0..<3) { number in
                      Button(action: {
                          flagTapped(number)
                          withAnimation() {
                              buttonNumber = spinToWin(number) ? number : 0
                              print(buttonNumber)
                          }
                      }) {

                          Image(countries[number])
                              .renderingMode(.original)
                              .flags()
                              .shadow(color: .black, radius: 5)
                              .rotation3DEffect(
                                  .degrees(Double(spinToWin(number) ?  360 : 0)),
                                  axis: (x: 0, y: 1, z: 0))
                              .animation(.default)
                      }
                  }

                  Text("Your Score: \(score)")
                      .foregroundColor(.white)
                      .font(.title)
              }
              Spacer()
          }
      }

      .alert(isPresented: $showingScore) {
          Alert(title: Text(scoreTitle), message: Text(alertMessage), dismissButton: .default(Text("Continue")) {
              askQuestion()
          })
      }
  }

  //  This method determines if we chose the correct answer & sets the Alert Message vaiables plus adjusts our score

  func flagTapped(_ number: Int) {
      if number == correctAnswer {
          scoreTitle = "Correct!"
          score += 10
          alertMessage = "10 Points added. New Score is: \(score)"
      } else {
          scoreTitle = "Wrong-O"
          score -= 5

          //            Yes, we know this switch statement is messy!

          switch correctAnswer {
          case 0:
              alertMessage = "5 points deducted. New Score is: \(score). The correct answer was top flag."
          case 1:
              alertMessage = "5 points deducted. New Score is: \(score). The correct answer was the middle flag."
          case 2:
              alertMessage = "5 points deducted. New Score is: \(score). The correct answer was bottom flag."
          default:
              alertMessage = "Error in program"
          }
      }
      showingScore = true
  }

  // Ths method creates a new round for us with new countries & a new correct answer
  func askQuestion() {
      countries.shuffle()
      correctAnswer = Int.random(in: 0...2)
      buttonNumber = 4
      }
      }
      struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
      ContentView()
      }
      }

3      

It might be due to the fact that you don't seem to have a "state" change. You have declared spinAmount as @State but then don't use it or change it.

You could potentially change your spinToWin function to return a Double.

  func spinToWin(_ number: Int) -> Double {
      if number == correctAnswer {
          spinAmount = 360.0
          return spinAmount
      } else {
          spinAmount = 0.0
          return spinAmount
      }
  }

and then when you set degrees you change it to:

.rotation3DEffect(
    .degrees(spinToWin(number)),
        axis: (x: 0, y: 1, z: 0))

You essentially need to tell Swift something changed for it to know that it has to do some update. Which is why, when you actually go to the next question, a state being monitored has changed, and the commands are executed. At least that's my understanding of what's going on. Hope that works/helps!

4      

Howdy,

Thank you so much for taking the time to review our code & provide a suggestion. While it didn't work out as intended, it did get us a step closer; plus like you, I was under the impression an @State needed to be modified in order for our View to update, however I was not observing this behavior but you have reaffired my belief in this functionality of SwiftUI.

Your solution has trigged a "Modfying state during view update, this will cause undefined behavior." warning.

Again, very much appreciate the help.

Baron

3      

I'll check the code later and compare to mine a bit, but maybe you can find some answer in another post about this:

https://www.hackingwithswift.com/forums/100-days-of-swiftui/day-34-conditional-animation/3824

Hope that helps in the meatime.

4      

I copied your code into a new project to test things out and see where the differences were. (the answer was in the link). Here goes:

1- .animation(.default) seems to cause 2 flags to rotate when you hit Continue on the alert. Not sure why but I tried it multiple times with that line commented out or active and it's the culprit. That's after fixing the setup.

2- You can keep spinToWin as you currently have it declared. We can use it later.

func spinToWin(_ number: Int) -> Bool {
      return number == correctAnswer
}

3- inside the action of our buttons we need to check if we should rotate. I used your testRotation variable there:

if testRotation {
    spinAmount += 360
}

That's before or after the print section. I would recommend changing the name to shouldRotate as it becomes clearer.

4- In flagTapped() we need to set our boolean depending on the answer. Therefore, if the user chose the correct answer we set it to true if it's the wrong answer we set it to false

5- in askQuestion() we set testRotation (or the new name) to false to make sure the game starts fresh.

6- And finally, we set the rotation animation according to these results like so:

.rotation3DEffect(.degrees(spinToWin(number) ? spinAmount : 0), axis: (x: 0, y: 1, z: 0))

When I tested the above it worked fine. I hope someone can explain why adding .animation(.default) causes 2 flags to rotate when get the next question.

Hope that helps!

4      

Howdy,

I will go over the link on my next stream (I do all my SwiftUI learning live) & thank you so much for the above...I thought I took at all of the print( )statements as I was merely using them to see values in the console vs putting in Breakpoints & checking values there.

What I can tell you is two flags were rotating, because it was rotating the one flag for the correctAnswer & then rotating the 2nd flag for the new correctAnswer for the next round...now, if the new correctAnswer (button 0, 1 or 2) was the same correctAnswer as the last round it wouldn't rotate depending on if you chose the ocrrect answer or wrong answer. I didn't figure that out until I was using a few print statements I deleted before posting the code.

A few of the variables were left over from the many previous attempts to solve this issue.

As I told my chat yesterday late in the program "Even if MarcusKay didn't get us over this hump, they took us down a new path towards a possible answer & even if that path didn't work out they reaffirmed our belief an @State chage would/should trigger a View to update." A win-win no matter what.

I can't wait to give this a go!

Big Fan,

Baron

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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.