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. I ended up creating static variables in my Card struct, which allows me to change the card colors via onTapGesture actions. 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. 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. The code in my answer shows how to use a ObservableObject
CardManager
to change all cards color resulting from your selection in 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()
}
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.