在SwiftUI中,在ForEach(0 .. <3)中,仅对轻击的按钮进行动画处理(并非对所有3个按钮进行动画处理),对其他2个进行不同的动画处理

时间:2020-06-22 10:39:55

标签: swift animation swiftui

在尝试为这些按钮设置动画之后,我放弃了并寻求帮助。 我只想为正确的按钮设置动画,如下所示: .rotation3DEffect(.degrees(self.animationAmount),轴:(x:0,y:1,z:0)) 并同时使其他两个按钮淡出25%。

当玩家单击错误的按钮时,我想 为错误的按钮设置动画,如下所示: .rotation3DEffect(.degrees(self.animationAmount),轴:(x:1,y:1,z:1))(或您认为可能会导致灾难的其他方式),而另两个则不予考虑。

在那之后,我希望警报显示。

下面是我的代码。我评论了我想做什么以及在可能的情况下。 一切都按我想要的方式工作,但动画无法播放。

非常感谢您的帮助

    import SwiftUI

struct ContentView: View {
   @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"]
        @State private var correctAnswer = Int.random(in: 0...2)
        @State private var showingScore = false
        @State private var scoreTitle = ""
        @State private var userScore = 0
        @State private var userTapped = ""
         @State private var animationAmount =  0.0
    
        var body: some View {
            ZStack {
                LinearGradient(gradient: Gradient(colors: [.blue, .black]), startPoint: .top, endPoint: .bottom)
                    .edgesIgnoringSafeArea(.all)
                
                VStack(spacing: 20) {
                    VStack {
                        Text("Tap the flag of...")
                            .foregroundColor(.white).font(.title)
                        Text(countries[correctAnswer])
                            .foregroundColor(.white)
                            .font(.largeTitle).fontWeight(.black)
                    }
                    
                    ForEach(0 ..< 3) { number in
                        Button(action: {
                                self.flagTapped(number)
                            if self.correctAnswer == number {
                                //animate the correct button only like this:
                                //.rotation3DEffect(.degrees(self.animationAmount), axis: (x: 0, y: 1, z: 0))
                                // and
                                // make the other two buttons fade out to 25% opacity
                            } else {
                                // animate the wrong button like this:
                                //.rotation3DEffect(.degrees(self.animationAmount), axis: (x: 1, y: 1, z: 1))
                            }
                        }) {
                            Image(self.countries[number])
                                .renderingMode(.original)
                                .clipShape(Capsule())
                                .overlay(Capsule().stroke(Color .black, lineWidth: 1))
                                .shadow(color: .black, radius: 2)
                        }
                    }
                     
                    Text ("your score is:\n \(userScore)").foregroundColor(.white).font(.title).multilineTextAlignment(.center)
                }
                
            }
            .alert(isPresented: $showingScore) {
                Alert(title: Text(scoreTitle), message: Text("You chose the flag of \(userTapped)\nYour score is now: \(userScore)"), dismissButton: .default(Text("Continue")) {
                    self.askQuestion()
                    })
            }
        }
        func flagTapped(_ number: Int) {
            userTapped = countries[number]
            if number == correctAnswer {
                scoreTitle = "Correct"
                userScore += 1
            } else {
                scoreTitle = "Wrong"
                userScore -= 1
            }
            showingScore = true
        }
        
        func askQuestion() {
            countries.shuffle()
            correctAnswer = Int.random(in: 0...2)
        }
    
    }

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

2 个答案:

答案 0 :(得分:1)

在解决此挑战时,我遇到了与您类似的问题。我想出了不使用DispatchQueue.main.asyncAfter的解决方案。我提出以下作为最终解决方案:

  1. 将正确选择的标志旋转360度,然后淡出其他标志,使其达到25%的不透明度。
  2. 用红色选择错误标志的背景,然后将其他标志淡出至不透明度的25%。

这是完整的解决方案(我对实现上述解决方案至关重要的部分发表评论):

import SwiftUI

// Create a custom view
struct FlagImage: View {
    var countryFlags: String
    
    var body: some View {
        Image(countryFlags)
            .renderingMode(.original)
            .clipShape(Capsule())
            .overlay(Capsule().stroke(Color.black, lineWidth: 1))
            .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...3)
    @State private var showingScore = false
    @State private var scoreTitle = ""
    
    @State private var userScore = 0
    
    // Properties for animating the chosen flag
    @State private var animateCorrect = 0.0
    @State private var animateOpacity = 1.0
    @State private var besidesTheCorrect = false
    @State private var besidesTheWrong = false
    @State private var selectedFlag = 0
    
    var body: some View {
        
        ZStack {
            LinearGradient(gradient: Gradient(colors: [.blue, .black]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.all)
            
            VStack(spacing: 30) {
                
                VStack {
                    Text("Tap on the flag!")
                        .foregroundColor(.white)
                        .font(.title)
                    
                    Text(countries[correctAnswer])
                        .foregroundColor(.white)
                        .font(.largeTitle)
                        .fontWeight(.black)
                }
                
                
                ForEach(0 ..< 4) { number in
                    Button(action: {
                        
                        self.selectedFlag = number
                        
                        self.flagTapped(number)
                        
                    }) {
                        
                        FlagImage(countryFlags: self.countries[number])
                    }
                    // Animate the flag when the user tap the correct one:
                    // Rotate the correct flag
                    .rotation3DEffect(.degrees(number == self.correctAnswer ? self.animateCorrect : 0), axis: (x: 0, y: 1, z: 0))
                    // Reduce opacity of the other flags to 25%
                    .opacity(number != self.correctAnswer && self.besidesTheCorrect ? self.animateOpacity : 1)
                    
                    // Animate the flag when the user tap the wrong one:
                    // Create a red background to the wrong flag
                    .background(self.besidesTheWrong && self.selectedFlag == number ? Capsule(style: .circular).fill(Color.red).blur(radius: 30) : Capsule(style: .circular).fill(Color.clear).blur(radius: 0))
                    // Reduce opacity of the other flags to 25% (including the correct one)
                    .opacity(self.besidesTheWrong && self.selectedFlag != number ? self.animateOpacity : 1)
                    
                }
                Spacer()
                
                Text("Your total score is: \(userScore)")
                    .foregroundColor(Color.white)
                    .font(.title)
                    .fontWeight(.black)
                
                Spacer()
                
            }
        }
        .alert(isPresented: $showingScore) {
            Alert(title: Text(scoreTitle), dismissButton: .default(Text("Continue")) {
                self.askQuestion()
                })
        }
        
    }
    
    func flagTapped(_ number: Int) {
        
        if number == correctAnswer {
            scoreTitle = "Correct!"
            
            userScore += 1
            
            // Create animation for the correct answer
            withAnimation {
                self.animateCorrect += 360
                self.animateOpacity = 0.25
                self.besidesTheCorrect = true
            }
        } else {
            scoreTitle = "Wrong!"
            
            // Create animation for the wrong answer
            withAnimation {
                self.animateOpacity = 0.25
                self.besidesTheWrong = true
            }
        }
        showingScore = true
    }

    func askQuestion() {
        // Return the booleans to false
        besidesTheCorrect = false
        besidesTheWrong = false
        countries = countries.shuffled()
        correctAnswer = Int.random(in: 0...3)
    }
    
}

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

基本上,我的解决方案是在“按钮”视图之后的视图修饰符(例如.rotation3DEffect(...).opacity(...).background(...))内添加三元运算符。棘手的部分是正确组合检查条件。

我更喜欢在withAnimation函数中添加flagTapped修饰符。如果用户选择正确或错误的标志,在这个地方我可以更好地控制动画。

我对原始挑战进行了小的更改:只需在视图中再添加一个标志。

用户按下正确和错误的标志时的最终结果是:

Screenshot

答案 1 :(得分:0)

我有同样的问题。这是我的解决方案(仅与您的代码不同):

ForEach:

ForEach(0 ..< 3, id: \.self){ number in
                Button(action: {
                    withAnimation{
                        self.tappedFlag = number
                        self.flagTapped(number)
                    }
                }){
                    FlagImage(imageName: self.countries[number])
                }
                .rotation3DEffect(.degrees(self.isCorrect && self.selcectedNumber == number ? 360 : 0), axis: (x: 0, y: 1, z: 0))
                .opacity(self.isFadeOutOpacity && self.selcectedNumber != number ? 0.25 : 1)
                    
                .rotation3DEffect(.degrees(self.wrongAnswer && number != self.correctAnswer ? 180 : 0), axis: (x: 1, y: 0, z: 0))
                .opacity(self.wrongAnswer && number != self.correctAnswer ? 0.25 : 1)
                
            }

警报:

.alert(isPresented: $showingScore){
        if scoreTitle == "Correct"{
            return Alert(title: Text(scoreTitle), message: Text("Your score is \(userScore)"), dismissButton: .default(Text("Continue")){
                    self.askQuestion()
                })
        }else{
            return Alert(title: Text(scoreTitle), message: Text("That is the flag of \(countries[tappedFlag]), you lost one point!"), dismissButton: .default(Text("Continue")){
                self.askQuestion()
            })
        }
    }

两个功能:

func flagTapped(_ number: Int){
    self.selcectedNumber = number
    self.alreadyTapped = true
    if number == correctAnswer{
        scoreTitle = "Correct"
        userScore += 1
        self.isCorrect = true
        self.isFadeOutOpacity = true
    }else{
        self.wrongAnswer = true
        scoreTitle = "Wrong"
        if userScore != 0{
            userScore -= 1
        }
    }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.showingScore = true
    }
}

func askQuestion(){
    countries = countries.shuffled()
    correctAnswer = Int.random(in: 0...2)
    self.isCorrect = false
    self.isFadeOutOpacity = false
    self.wrongAnswer = false
}

您必须声明一些新变量:)

希望我能帮上忙。

PS:Youtube上有一个100DaysOfSwiftUI的播放列表,其中包含几乎每个任务的解决方案。

https://www.youtube.com/watch?v=9AUGceRIUSA&list=PL3pUvT0fmHNhb3qcpvuym6KeM12eQK3T1