简体   繁体   中英

SwiftUI - Animate a view on button press

I have just finished lesson 6 of #hackingwithswift. I'm going mad over a challenge that shouldn't be that hard at all...

Here I have a working game called GuessTheFlag wherein the user must select the correct flag indicated then receives an alert before being passed to the next round.

My problem is simple: I would like to have the incorrect flag views become opaque and have the correct flag view rotate 360 degrees when an answer is chosen. This should all happen behind the alert until the user clicks 'continue'.

Optional: I would also like to remove the alert altogether and have the game move on to the next round after the rotating animation has finished. Can I bind the round change to the end of the animation? Or do I have to use some sort of sleep() command?

import SwiftUI

struct FlagView: View {
    var flag: String
    
    var body: some View {
        Image(flag)
            .renderingMode(.original)
            .clipShape(Capsule())
            .overlay(Capsule().stroke(Color.black, lineWidth: 2))
            .shadow(color: .black, radius: 2)
    }
    
}

struct ContentView: View {
    @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)
    @State private var showingScore = false
    @State private var scoreTitle = ""
    @State private var score = 0
    @State private var animate = false
    
    var alert: Alert {
        if scoreTitle == "Wrong" {
            return Alert(title: Text(scoreTitle), message: Text("The correct answer was \(countries[correctAnswer])"), dismissButton: .default(Text("Restart")) {
                self.askQuestion()
                animate = false
            })
        }
        else {
            return Alert(title: Text(scoreTitle), message: Text("Your Score is \(score)"), dismissButton: .default(Text("Continue")) {
                self.askQuestion()
                animate = false
            })
        }
        
    }

    var body: some View {
        ZStack{
            LinearGradient(gradient: Gradient(colors: [.black,.blue, .orange]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.all)
            
            VStack(spacing: 40) {
                VStack{
                    Text("Tap the flag of")
                        .foregroundColor(.white)
                    Text(countries[correctAnswer])
                        .font(.largeTitle)
                        .fontWeight(.black)
                        .foregroundColor(.white)
                }
                
                ForEach(0 ..< 3){ number in
                    Button(action: {
                        withAnimation {
                            self.flagTapped(number)
                            animate.toggle()
                        }
                    }) {
                        FlagView(flag: self.countries[number])
                    }
                }
                
                Text("Your Score is \(score)")
                    .font(.headline)
                    .fontWeight(.black)
                    .foregroundColor(Color.white)
                
                Spacer()
            }
        }
        .alert(isPresented: $showingScore) {
            alert
        }
    }

    func flagTapped(_ number: Int) {
        if number == correctAnswer {
            scoreTitle = "Correct"
            score += 1
            
        } else {
            scoreTitle = "Wrong"
            score = 0
            
        }
        
        showingScore = true
        
    }
    
    func askQuestion() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
    }
}


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

Any help would be appreciated! Thanks in advance

Just apply the view modifier opacity and rotationEffect on FlagView and instead of using discrete values (such as .opacity(0.1) ) make it conditional:

.opacity((self.animate && number != correctAnswer) ? 0.1 : 1.0)

The animation part will be automatically done be SwiftUI. Please see my working example below:

import SwiftUI

struct FlagView: View {
    var flag: String
    
    var body: some View {
        Text(flag)
    }
    
}

struct ContentView: View {
    @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)
    @State private var showingScore = false
    @State private var scoreTitle = ""
    @State private var score = 0
    @State private var animate = false
    
    var alert: Alert {
        if scoreTitle == "Wrong" {
            return Alert(title: Text(scoreTitle), message: Text("The correct answer was \(countries[correctAnswer])"), dismissButton: .default(Text("Restart")) {
                self.askQuestion()
            })
        }
        else {
            return Alert(title: Text(scoreTitle), message: Text("Your Score is \(score)"), dismissButton: .default(Text("Continue")) {
                self.askQuestion()
            })
        }
        
    }

    var body: some View {
        ZStack{
            LinearGradient(gradient: Gradient(colors: [.black,.blue, .orange]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.all)
            
            VStack(spacing: 40) {
                VStack{
                    Text("Tap the flag of")
                        .foregroundColor(.white)
                    Text(countries[correctAnswer])
                        .font(.largeTitle)
                        .fontWeight(.black)
                        .foregroundColor(.white)
                }
                
                ForEach(0 ..< 3){ number in
                    Button(action: {
                        withAnimation(.easeInOut(duration: 0.5)) {
                            self.animate = true
                        }
                        self.flagTapped(number)
                    }) {
                        FlagView(flag: self.countries[number])
                            .opacity((self.animate && number != correctAnswer) ? 0.1 : 1.0)
                            .rotationEffect(.init(degrees: (self.animate && number == correctAnswer) ? 360.0 : 0.0))
                    }
                }
                
                Text("Your Score is \(score)")
                    .font(.headline)
                    .fontWeight(.black)
                    .foregroundColor(Color.white)
                
                Spacer()
            }
        }
        .alert(isPresented: $showingScore) {
            alert
        }
    }

    func flagTapped(_ number: Int) {
        if number == correctAnswer {
            scoreTitle = "Correct"
            score += 1
            
        } else {
            scoreTitle = "Wrong"
            score = 0
            
        }
        
        showingScore = true
        
    }
    
    func askQuestion() {
        self.animate = false
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
    }
}


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

For your optional question you could introduce a custom modifier (eg as described here ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM