【问题标题】:Swiftui - Need to spin the selected button onlySwiftui - 只需要旋转选定的按钮
【发布时间】:2020-08-23 14:33:58
【问题描述】:

我正在关注 Hackingwithswift (https://www.hackingwithswift.com/books/ios-swiftui/animation-wrap-up) 的 100 天 swift 课程。项目 6 挑战 1 请求“返回猜旗项目并添加一些动画:当您点击正确的旗帜时,使其在 Y 轴上旋转 360 度”。 选择正确答案后,我能够使按钮旋转,但我不知道如何仅使选定的按钮旋转。

这是创建按钮的循环:

 ForEach(0..<3){ number in
                
                Button(action:{
                    self.flagTapped(number)
                }){
                    FlagImage(number: number, countries: self.countries)
                }
                .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 0, y: 1, z: 0))
                }

这个flagTapped函数:

 func flagTapped(_ number: Int){
    if number == correctAswer{
        scoreTitle = "Correct"
        self.score += 1
        withAnimation() {
            self.animationAmount += 360
        }
    }
    else{
        scoreTitle = "Wrong. That is the flag of \(self.countries[number])"
        self.score -= 1
    }
    showingMessage = true
}

感谢您的帮助

我发布了整个代码,希望得到一个答案,使第二个建议的选项起作用

 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 showingMessage = false
@State private var scoreTitle = ""
@State private var score = 0

@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:30){
            VStack{
                Text("Tap the flag of")
                    .foregroundColor(.white)
                Text(countries[correctAnswer])
                    .font(.largeTitle)
                    .fontWeight(.black)
                    .foregroundColor(.white)
            }
            
            ForEach(0 ..< 3) { number in
                
                if number == self.correctAnswer {
                    Button(action: {
                        self.flagTapped(number)
                    }) {
                        FlagImage(number: number, countries: self.countries)
                    }
                    .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 0, y: 1, z: 0))
                } else {
                    Button(action: {
                        self.flagTapped(number)
                    }) {
                        FlagImage(number: number, countries: self.countries)
                    }
                    .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 1, y: 0, z: 0))
                }
            }
            Text("Your score is \(score)")
                .foregroundColor(.white)
            Spacer()
        }
    }
    .alert(isPresented: $showingMessage){
        Alert(title: Text(scoreTitle), message: Text(""), dismissButton: .default(Text("Continue")){
            self.askQuestion()
        })
    }
}

func flagTapped(_ number: Int){
    if number == correctAnswer{
        scoreTitle = "Correct"
        self.score += 1
        withAnimation() {
            self.animationAmount += 360
        }
    }
    else{
        scoreTitle = "Wrong. That is the flag of \(self.countries[number])"
        self.score -= 1
        withAnimation() {
            self.animationAmount += 360
        }
    }
    showingMessage = true
}

func askQuestion(){
    countries.shuffle()
    correctAnswer = Int.random(in: 0...2)
}
}

struct FlagImage: View {
var number: Int
var countries:[String]=[]

var body: some View {
    Image(countries[number])
    .renderingMode(.original)
    .clipShape(Capsule())
    .overlay(Capsule().stroke(Color.black, lineWidth: 1))
    .shadow(color: .black, radius: 2)
}
}

【问题讨论】:

    标签: button swiftui


    【解决方案1】:

    您将.rotation3DEffect 应用到每个按钮,但动画不会发生,直到您更改self.animationAmount

    correctAnswer 只能使用self.animationAmount

    变化:

    .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 0, y: 1, z: 0))
    

    到:

    .rotation3DEffect(.degrees(number == correctAnswer ? self.animationAmount : 0), axis: (x: 0, y: 1, z: 0))
    

    另一种方法是:(唉,这似乎只适用于 Xcode 12b5):

    ForEach(0 ..< 3) { number in
        if number == correctAnswer {
            Button(action: {
                self.flagTapped(number)
            }) {
                FlagImage(number: number, countries: self.countries)
            }
            .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 0, y: 1, z: 0))
        } else {
            Button(action: {
                self.flagTapped(number)
            }) {
                FlagImage(number: number, countries: self.countries)
            }
        }
    }
    

    由于Button 代码的重复,这似乎不太令人满意,但如果您希望将不同的动画应用于未选择的按钮,它可能会很方便。


    Xcode 11.6 中的代码问题

    Xcode 11.6 的代码问题确实是一个问题,你会在 SwiftUI 中反复遇到,除非你小心并以正确的方式做事。

    当您使用ForEach 创建视图列表时,SwiftUI 能够唯一标识项目非常重要。在原始代码中,我们使用了ForEach(0..&lt;3),而这些是可怕的ids,因为它们在标志更改时不会更改。

    要解决此问题,最好的办法是将ForEachIdentifiable 项目(具有唯一id 的项目)的数组一起使用。

    我用Flag 数组替换了国家名称数组。 Flag 是一个 struct,它具有唯一的 idcountry 名称。此外,Flag 符合 Identifiable(这意味着它提供唯一的 id)。因为物品是可识别的,所以我们可以通过ForEach(countries.prefix(3)) 显示前三个国家的国旗。而且因为flags是Identifiable,所以当数组发生变化时,视图肯定会正确重绘。

    看看我所做的更改。请注意,我添加了一个额外的动画,它会导致错误的标志在正确的标志旋转时消失。开始新游戏时需要恢复opacity设置,否则旗帜仍然不可见。

    struct FlagImage: View {
        var country: String
        
        var body: some View {
            Image(country)
                .renderingMode(.original)
                .clipShape(Capsule())
                .overlay(Capsule().stroke(Color.black, lineWidth: 1))
                .shadow(color: .black, radius: 2)
        }
    }
    
    struct Flag: Identifiable {
        let id = UUID()
        let country: String
    }
    
    struct ContentView: View {
        @State private var showingScore = false
        @State private var scoreTitle = ""
        
        @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled().map(Flag.init)
        @State private var correctAnswer = Int.random(in: 0...2)
        @State private var score = 0
        @State private var alertMessage = ""
        @State private var animationAmount = 0.0
        @State private var animatedOpacity = 1.0
        
        var body: some View {
            ZStack {
                    LinearGradient(gradient: Gradient(colors: [.blue, .black]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.all)
                VStack {
                    VStack {
                        Text("Tap the flag of ").foregroundColor(.white)
                        Text("\(countries[correctAnswer].country) ")
                            .foregroundColor(.white)
                            .font(.largeTitle)
                            .fontWeight(.black)
                    }
                    
                    ForEach(countries.prefix(3)) { flag in
                        Group {
                            if flag.country == self.countries[self.correctAnswer].country {
                                Button(action: {
                                    self.flagTapped(flag.country)
                                }) {
                                    FlagImage(country: flag.country)
                                }
                                .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 0, y: 1, z: 0))
                            } else {
                                Button(action: {
                                    self.flagTapped(flag.country)
                                }) {
                                    FlagImage(country: flag.country)
                                }
                                .opacity(self.animatedOpacity)
                            }
                        }
                    }
                    
                    Text("Score: \(score)").foregroundColor(.white)
                    
                    Spacer()
                }
            }
            .alert(isPresented: $showingScore) {
                Alert(title: Text(scoreTitle), message: Text(alertMessage), dismissButton: .default(Text("Continue")) {
                    self.askQuestion()
                })
            }
        }
        
        func flagTapped(_ country: String) {
            if country == countries[correctAnswer].country {
                score += 1
                scoreTitle = "Correct!"
                alertMessage = "Your score is now \(score)"
                withAnimation {
                    animationAmount += 360
                    animatedOpacity = 0
                }
            } else {
                scoreTitle = "Wrong."
                alertMessage = "That is the flag of \(country)"
                score -= 1
            }
            
            showingScore = true
        }
        
        func askQuestion() {
            countries.shuffle()
            correctAnswer = Int.random(in: 0...2)
            self.animatedOpacity = 1.0
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    代码的最小修复:

    现在我已经确定了为什么这在 Xcode 11.6 中不起作用,下面是对您的代码进行的最小更改以使其起作用:

    1. 更改ForEach 循环以迭代包含(offset, element) 的元组的Array,并通过添加, id: \.element 使用国家名称作为id。由于国家/地区名称是唯一的,因此可以确保国旗在更改时更新。
    2. 在闭包中,选择元组的各个部分并将它们命名为numbername 以便清楚起见。
    3. 将带有按钮的if 语句放在Group { } 内,因为Xcode 11.6 中的SwiftUI 无法单独处理if
        ForEach(Array(self.countries.prefix(3).enumerated()), id: \.element) { number, name in
            Group {
                if number == self.correctAnswer {
                    Button(action: {
                        self.flagTapped(number)
                    }) {
                        FlagImage(number: number, countries: self.countries)
                    }
                    .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 0, y: 1, z: 0))
                } else {
                    Button(action: {
                        self.flagTapped(number)
                    }) {
                        FlagImage(number: number, countries: self.countries)
                    }
                    .rotation3DEffect(.degrees(self.animationAmount), axis: (x: 1, y: 0, z: 0))
                }
            }
        }
    

    【讨论】:

    • 第二个选项不能按预期工作。我希望它可以工作,但我不知道出了什么问题。它不再为按钮设置动画。另外,标志不会重新加载(屏幕始终显示相同的标志)
    • 我刚刚发布了整个代码。你能看一下吗?谢谢!
    • 很抱歉删除了正确答案。我试图在后续 cmets 上遵循堆栈溢出的准则。我在第一个按钮上看到了你的动画。我的后续评论是指您的第二个选项,其中包含“if else”语句中的按钮。我想要其他按钮的第二个动画(如果您注意到,我更改了未选中按钮的 x 和 y 值)
    • 版本 11.6 (11E708)
    • 我正在运行您的代码,并且标志图像在新游戏中不会改变。你看到这种行为了吗?
    猜你喜欢
    • 2021-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 2011-12-25
    • 1970-01-01
    • 2015-05-01
    • 1970-01-01
    相关资源
    最近更新 更多