简体   繁体   English

使用 onTapGesture 更改 UI 的颜色

[英]Changing color of UI with onTapGesture

I've been trying to find a way to change the color of every flashcard in my app.我一直在尝试找到一种方法来更改我的应用程序中每张抽认卡的颜色。 I am struggling with understanding how to use @Binding, which I believe is the best solution.我正在努力理解如何使用@Binding,我认为这是最好的解决方案。 I ended up creating static variables in my Card struct, which allows me to change the card colors via onTapGesture actions.我最终在我的 Card 结构中创建了 static 变量,这允许我通过 onTapGesture 操作更改卡 colors。 It seems very slow and I know there has to be a better way to accomplish this.这似乎很慢,我知道必须有更好的方法来实现这一点。 Below is my code.下面是我的代码。 The action for changing the color is inside the Circle() in ContentView.更改颜色的操作在 ContentView 中的 Circle() 内部。 Thank you in advance.先感谢您。 Also could someone please tell me how to resize screenshots to post in here?也有人可以告诉我如何调整屏幕截图的大小以在此处发布吗? The solutions online make my screenshots super blurry so I hate uploading them在线解决方案使我的屏幕截图超级模糊,所以我讨厌上传它们

CardView卡片视图

import SwiftUI

struct CardView: View {
    let card: Card
    var removal: (() -> Void)? = nil
    
    @State private var isShowingAnswer = false
    @State private var changeColors = false
    @State private var offset = CGSize.zero
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 25, style: .continuous)
                .fill(LinearGradient(colors: [Card.gradCs[Card.selec][0].opacity(1 - Double(abs(offset.width / 50))), Card.gradCs[Card.selec][1].opacity(1 - Double(abs(offset.width / 50)))], startPoint: .topLeading, endPoint: .bottomTrailing))
                .background(RoundedRectangle(cornerRadius: 25, style: .continuous).fill(offset.width > 0 ? .green : .red))
                .shadow(color: Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.25)), radius:6, x:1, y:8)
            
            VStack(spacing: 20){
                Text(card.prompt)
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .foregroundColor(.white.opacity(0.8))
                
                if isShowingAnswer {
                    Text(card.answer)
                        .font(.title)
                        .fontWeight(.semibold)
                        .foregroundColor(Color(#colorLiteral(red: 0.8947916626930237, green: 0.8666666746139526, blue: 1, alpha: 1)))
                }
            }
            .padding()
            .multilineTextAlignment(.center)
        }
        .frame(width: 450, height: 250)
        .rotationEffect(.degrees(Double(offset.width / 5)))
        .offset(x: offset.width * 5, y: 0)
        .opacity(2 - Double(abs(offset.width / 50)))
        .gesture(
            DragGesture()
                .onChanged { gesture in
                    offset = gesture.translation
                }
                .onEnded { _ in
                    if abs(offset.width) > 100 {
                        removal?()
                    } else {
                        offset = .zero
                    }
                }
            )
        .onTapGesture {
            isShowingAnswer.toggle()
        }
    }
}

struct CardView_Previews: PreviewProvider {
    static var previews: some View {
        CardView(card: Card.example)
            .previewInterfaceOrientation(.portraitUpsideDown)
    }
}

ContentView内容视图

import SwiftUI

extension View {
    func stacked(at position: Int, in total: Int) -> some View {
        let offset = Double(total - position)
        return self.offset(x: 0, y: offset * 10)
    }
}

struct ContentView: View {
    @Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor
    @State private var cards = Array(repeating: Card.example, count: 10)
    
    @State private var timeRemaining = 100

    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    @Environment(\.scenePhase) var scenePhase
    @State private var isActive = true
    
    var body: some View {
        ZStack {
            VStack {
                HStack {
                    Text("\(timeRemaining / 60):\(timeRemaining % 60, specifier: "%02d")")
                        .font(.title)
                        .fontWeight(.semibold)
                        .foregroundColor(.white)
                        .padding(.horizontal, 20)
                        .padding(.vertical, 5)
                        .background(.black.opacity(0.75))
                        .clipShape(Capsule())
                    
                    Circle()
                        .fill(LinearGradient(colors: Card.gradCs[Card.selec], startPoint: .topLeading, endPoint: .bottomTrailing))
                        .onTapGesture {
                            withAnimation {
                                if Card.selec == 0 {
                                    Card.selec = 1
                                } else {
                                    Card.selec = 0
                                }
                            }
                        }
                        .frame(width: 40, height: 40)
                }
                ZStack {
                    ForEach(0 ..< cards.count, id: \.self) { index in
                        CardView(card: cards[index]) {
                            withAnimation {
                                removeCard(at: index)
                                print(self.offset())
                            }
                        }
                        .stacked(at: index, in: cards.count)
                    }
                }
                .allowsHitTesting(timeRemaining > 0)
                
                if cards.isEmpty {
                    Button("Start Again", action: resetCards)
                        .padding()
                        .background(.white)
                        .foregroundColor(.black)
                        .clipShape(Capsule())
                }
            }
            if differentiateWithoutColor {
                VStack {
                    Spacer()
                    
                    HStack {
                        Image(systemName: "xmark.circle")
                            .padding()
                            .background(.black.opacity(0.7))
                            .clipShape(Circle())
                        
                        Spacer()
                        
                        Image(systemName: "checkmark.circle")
                            .padding()
                            .background(.black.opacity(0.7))
                            .clipShape(Circle())
                    }
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                }
            }
        }
        .onReceive(timer) { time in
            guard isActive else { return }
            // Use if let when the non-nil case is valid. Use guard when the nil case represents some sort of error.
            // use guard when there should only be one result.. use if when you need ELSE to do something
            if timeRemaining > 0 {
                timeRemaining -= 1
            }
        }
        .onChange(of: scenePhase) { newPhase in
            if newPhase == .active {
                if cards.isEmpty == false {
                    isActive = true
                }
            } else {
                isActive = false
            }
        }
    }
    
    func removeCard(at index: Int) {
        cards.remove(at: index)
        
        if cards.isEmpty {
            isActive = false
        }
    }
    
    func resetCards() {
        cards = Array(repeating: Card.example, count: 10)
        timeRemaining = 100
        isActive = true
    }
}

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

Card卡片

import Foundation
import SwiftUI

struct Card {
    let prompt: String
    let answer: String
    
    static var example = Card(prompt: "What is the name of Chicago's NFL Team?", answer: "Da Bears")
    static var gradCs = [[Color(#colorLiteral(red: 0.6039215922355652, green: 0.49803921580314636, blue: 1, alpha: 1)), Color(#colorLiteral(red: 1, green: 0.49803924560546875, blue: 1, alpha: 1))], [Color(#colorLiteral(red: 0.6039215922355652, green: 1, blue: 1, alpha: 1)), Color(#colorLiteral(red: 0.6039215922355652, green: 0.49803921580314636, blue: 1, alpha: 1))]]
    static var selec = 0
}

binding is a possible way to change colors, but I prefer the use of ObservableObject to manage the set of Cards and their color selection.绑定是更改 colors 的一种可能方式,但我更喜欢使用ObservableObject来管理卡片集及其颜色选择。 The code in my answer shows how to use a ObservableObject CardManager to change all cards color resulting from your selection in Circle .我的答案中的代码显示了如何使用ObservableObject CardManager更改因您在Circle中的选择而产生的所有卡片颜色。

// manages the cards and all cards color selection
class CardManager: ObservableObject {
    let gradCs = [[Color(#colorLiteral(red: 0.6039215922355652, green: 0.49803921580314636, blue: 1, alpha: 1)), Color(#colorLiteral(red: 1, green: 0.49803924560546875, blue: 1, alpha: 1))], [Color(#colorLiteral(red: 0.6039215922355652, green: 1, blue: 1, alpha: 1)), Color(#colorLiteral(red: 0.6039215922355652, green: 0.49803921580314636, blue: 1, alpha: 1))]]
    
    @Published var cards = [Card]()
    @Published var selec = 0
  
    func getColorSet() -> [Color] {
        return gradCs[selec]
    }
}

struct Card {
    let prompt: String
    let answer: String

    static var example = Card(prompt: "What is the name of Chicago's NFL Team?", answer: "Da Bears")
}

extension View {
    func stacked(at position: Int, in total: Int) -> some View {
        let offset = Double(total - position)
        return self.offset(x: 0, y: offset * 10)
    }
}

struct ContentView: View {
    @Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor
    @StateObject var manager = CardManager()  // <--- here
    
    @State private var timeRemaining = 100

    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    @Environment(\.scenePhase) var scenePhase
    @State private var isActive = true
    
    var body: some View {
        ZStack {
            VStack {
                HStack {
                    Text("\(timeRemaining / 60):\(timeRemaining % 60, specifier: "%02d")")
                        .font(.title)
                        .fontWeight(.semibold)
                        .foregroundColor(.white)
                        .padding(.horizontal, 20)
                        .padding(.vertical, 5)
                        .background(.black.opacity(0.75))
                        .clipShape(Capsule())
                    
                    Circle()
                        .fill(LinearGradient(colors: manager.getColorSet(), startPoint: .topLeading, endPoint: .bottomTrailing))
                        .onTapGesture {
                            withAnimation {
                                if manager.selec == 0 {
                                    manager.selec = 1
                                } else {
                                    manager.selec = 0
                                }
                            }
                        }
                        .frame(width: 40, height: 40)
                }
                ZStack {
                    ForEach(0 ..< manager.cards.count, id: \.self) { index in
                        CardView(card: manager.cards[index]) {
                            withAnimation {
                                removeCard(at: index)
                                print(self.offset())
                            }
                        }
                        .stacked(at: index, in: manager.cards.count)
                    }
                }
                .allowsHitTesting(timeRemaining > 0)
                
                if manager.cards.isEmpty {
                    Button("Start Again", action: resetCards)
                        .padding()
                        .background(.white)
                        .foregroundColor(.black)
                        .clipShape(Capsule())
                }
            }
            if differentiateWithoutColor {
                VStack {
                    Spacer()
                    
                    HStack {
                        Image(systemName: "xmark.circle")
                            .padding()
                            .background(.black.opacity(0.7))
                            .clipShape(Circle())
                        
                        Spacer()
                        
                        Image(systemName: "checkmark.circle")
                            .padding()
                            .background(.black.opacity(0.7))
                            .clipShape(Circle())
                    }
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                }
            }
        }
        .onReceive(timer) { time in
            guard isActive else { return }
            // Use if let when the non-nil case is valid. Use guard when the nil case represents some sort of error.
            // use guard when there should only be one result.. use if when you need ELSE to do something
            if timeRemaining > 0 {
                timeRemaining -= 1
            }
        }
        .onChange(of: scenePhase) { newPhase in
            if newPhase == .active {
                if manager.cards.isEmpty == false {
                    isActive = true
                }
            } else {
                isActive = false
            }
        }
        .onAppear {
            manager.cards = Array(repeating: Card.example, count: 10)  // <--- here
        }
        .environmentObject(manager)  // <--- here
    }
    
    func removeCard(at index: Int) {
        manager.cards.remove(at: index)
        
        if manager.cards.isEmpty {
            isActive = false
        }
    }
    
    func resetCards() {
        manager.cards = Array(repeating: Card.example, count: 10)
        timeRemaining = 100
        isActive = true
    }
}

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

struct CardView: View {
    @EnvironmentObject var manager: CardManager // <--- here
    let card: Card
    var removal: (() -> Void)? = nil
    
    @State private var isShowingAnswer = false
    @State private var changeColors = false
    @State private var offset = CGSize.zero
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 25, style: .continuous)
                .fill(LinearGradient(colors: [manager.getColorSet()[0].opacity(1 - Double(abs(offset.width / 50))), manager.getColorSet()[1].opacity(1 - Double(abs(offset.width / 50)))], startPoint: .topLeading, endPoint: .bottomTrailing))
                .background(RoundedRectangle(cornerRadius: 25, style: .continuous).fill(offset.width > 0 ? .green : .red))
                
                .background(RoundedRectangle(cornerRadius: 25, style: .continuous).fill(offset.width > 0 ? .green : .red))
                .shadow(color: Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.25)), radius:6, x:1, y:8)
            
            VStack(spacing: 20){
                Text(card.prompt)
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .foregroundColor(.white.opacity(0.8))
                
                if isShowingAnswer {
                    Text(card.answer)
                        .font(.title)
                        .fontWeight(.semibold)
                        .foregroundColor(Color(#colorLiteral(red: 0.8947916626930237, green: 0.8666666746139526, blue: 1, alpha: 1)))
                }
            }
            .padding()
            .multilineTextAlignment(.center)
        }
        .frame(width: 450, height: 250)
        .rotationEffect(.degrees(Double(offset.width / 5)))
        .offset(x: offset.width * 5, y: 0)
        .opacity(2 - Double(abs(offset.width / 50)))
        .gesture(
            DragGesture()
                .onChanged { gesture in
                    offset = gesture.translation
                }
                .onEnded { _ in
                    if abs(offset.width) > 100 {
                        removal?()
                    } else {
                        offset = .zero
                    }
                }
            )
        .onTapGesture {
            isShowingAnswer.toggle()
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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