在SwiftUI中,在ForEach(0 ..〈3)中,仅动画点击的按钮(不是所有3个),以不同的方式动画其他2个

mspsb9vt  于 2023-05-05  发布在  Swift
关注(0)|答案(3)|浏览(226)

经过一整天的尝试动画这些按钮,我给予了,并寻求帮助。我想动画正确的按钮只像这样:.rotation3DEeffect(.degrees(self.animationAmount),axis:(x:0,y:1,z:0)),同时使其他两个按钮淡出到25%不透明度。
当玩家点击错误的按钮时,我想像这样动画错误的按钮:.rotation3DEeffect(.degrees(self.animationAmount),axis:(x:l,y:l,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()
    }
}
relj7zay

relj7zay1#

我也有同样的问题。以下是我的解决方案(只有代码与你的不同):
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

rkue9o1l

rkue9o1l2#

我在解决这个问题时遇到了和你类似的问题。我想出了一个不使用DispatchQueue.main.asyncAfter的解决方案。我做了以下作为最终解决方案:
1.旋转360度的正确选择的旗帜,并淡出其他旗帜,以25%的不透明度。
1.用红色模糊错误旗帜的背景,并将其他旗帜淡出到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()
    }
}

基本上,我的解决方案是在Button视图之后的视图修饰符中添加三元操作符(例如,.rotation3DEffect(...).opacity(...).background(...))。棘手的部分是正确合并检查条件。
我更喜欢将withAnimation修饰符添加到flagTapped函数中。在这个地方,如果用户选择了正确或错误的标志,我可以更好地控制动画。
我对最初的挑战做了一个小改动:只需在视图中添加一个标记。
当用户按下正确和错误标志时的最终结果如下:


c7rzv4ha

c7rzv4ha3#

这是一个老问题,但我想我还是找到了部分解决方案,这可能对未来的学生也有帮助。我的解决方案不是一个完美的,它是有点笨拙,但也许它可以帮助别人想出一个更好的。我从任务本身看到的是,你必须为每个按钮设置动画并淡出那些没有被点击的按钮。但是,如果您将动画应用于使用forEach创建的所有视图,并且它们都附加到同一个变量,则将为所有视图触发动画:我的解决方案是将这个动画变量拆分成一个带有标志索引的数组,并仅更改附加到已点击或未点击标志的变量。

这是我的完整代码:

struct ContentView: View {
@State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Monaco", "Nigeria", "Poland", "Spain", "UK", "US"].shuffled()
@State private var title = ""
@State private var isDisplayed = false
@State private var rightAnswer = Int.random(in: 0...2)
@State private var messageText = ""
@State private var score = 0
@State private var answersCount = 0
@State private var modalButtonText = ""
@State private var animationRotation = [0.0, 0.0, 0.0]
@State private var viewAnimation = 0.0
@State private var opacity = [1.0, 1.0, 1.0]

var body: some View {
    ZStack {
        Color(red: 239, green: 239, blue: 239)
            .ignoresSafeArea()
        VStack (spacing: 50){
            Spacer()
            VStack {
                Text("Guess the Flag")
                    .font(.subheadline.weight(.semibold))
                Text(countries[rightAnswer])
                    .font(.largeTitle.bold())
            }
            VStack (spacing: 30) {
        
                ForEach(0...2, id: \.self) {country in
                    Button {
                        checkCard(country)
                    } label: {
                        FlagView(index: country, arrayOfCountries: countries)
                    }.rotation3DEffect(Angle(degrees: animationRotation[country]), axis: (x: 0, y: 1, z: 0)).opacity(opacity[country])
                }.rotation3DEffect(Angle(degrees: viewAnimation), axis: (x: 0, y: 1, z: 0))
                Spacer()
                Text("Score: \(score)").TitleStyle()
                Spacer()
            }
        }
    }
    .alert(title, isPresented: $isDisplayed) {
        Button(modalButtonText) {
            newQuestion()
        }
    } message: {
        Text(messageText)
    }
}

func checkCard(_ number: Int) {
    
    if number == rightAnswer {
        modalButtonText = "Continue"
        title = "Correct"
        messageText = "You scored 1 point"
        score += 1
    } else {
        modalButtonText = "Continue"
        title = "Incorrect"
        messageText = "You lost 1 point"
        if score > 0 {
            score -= 1
        }
    }
    if answersCount > 8 {
        modalButtonText = "Restart"
        messageText = "This was the last one"
    }
    
    withAnimation(.easeIn(duration: 1.0)) {
        animationRotation[number] -= 360
        for i in 0...2 {
            if i != number {
                opacity[i] = 0.25
            }
        }
    }
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
        isDisplayed = true
    }

}

func newQuestion(){

    countries.shuffle()
    rightAnswer = Int.random(in: 0...2)
    for i in 0...2 {
        opacity[i] = 1.0
    }
    
    withAnimation(.easeIn(duration: 0.5)) {
        viewAnimation += 180
    }
    if answersCount > 8 {
        score = 0
        answersCount = 0
    } else {
        answersCount += 1
    }

}

}这些是解决方案部分:

@State private var animationRotation = [0.0, 0.0, 0.0]
@State private var opacity = [1.0, 1.0, 1.0]

这里我们创建两个具有相关值的数组变量

ForEach(0...2, id: \.self) {country in
                    Button {
                        checkCard(country)
                    } label: {
                        FlagView(index: country, arrayOfCountries: countries)
                    }.rotation3DEffect(Angle(degrees: animationRotation[country]), axis: (x: 0, y: 1, z: 0)).opacity(opacity[country])
                }.rotation3DEffect(Angle(degrees: viewAnimation), axis: (x: 0, y: 1, z: 0))

在ForEach视图中,我们调用将这些变量分配给每个动画修改器属性

withAnimation(.easeIn(duration: 1.0)) {
        animationRotation[number] -= 360
        for i in 0...2 {
            if i != number {
                opacity[i] = 0.25
            }
        }
    }
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
        isDisplayed = true
    }

设置延迟,这样我就可以显示动画之前,警报弹出,并改变不透明的其他标志,没有选择与循环

相关问题