Reputation: 41
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
Upvotes: 0
Views: 3243
Reputation: 659
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 (e.g. as described here).
Upvotes: 1