Learning Time : 1h 15min
My Github : Flashzilla Source Code
func addCard() {
let trimmedPrompt = newPrompt.trimmingCharacters(in: .whitespaces)
let trimmedAnswer = newAnswer.trimmingCharacters(in: .whitespaces)
guard trimmedPrompt.isEmpty == false && trimmedAnswer.isEmpty == false else { return }
let card = Card(prompt: trimmedPrompt, answer: trimmedAnswer)
cards.insert(card, at: 0)
saveData()
newPrompt = ""
newAnswer = ""
}
offset
back to 0 immediately, even though the card hasn’t animated yet. You might solve this with a ternary within a ternary, but a custom modifier will be cleaner.)struct CardView: View {
...
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(
differentiateWithoutColor
? .white
: .white.opacity(2 - Double(abs(offset.width / 50)))
)
.background(
differentiateWithoutColor
? nil
: RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(of: offset)
)
.shadow(radius: 10)
VStack {
...
}
}
...
}
}
extension Shape {
func fill(of offset: CGSize) -> some View {
if offset.width == 0 {
return self.fill(.white)
} else if offset.width < 0 {
return self.fill(.red)
} else {
return self.fill(.green)
}
}
}
ForEach
loop, because relying on simple integers isn’t enough – your cards need to be uniquely identifiable.struct Card: Codable, Identifiable {
var id = UUID()
let prompt: String
let answer: String
static let example = Card(prompt: "Who played the 13th Doctor in Doctor Who?", answer: "Jodie Whittaker")
}
struct CardView: View {
let card: Card
var removal: ((Bool) -> Void)? = nil
...
var body: some View {
ZStack {
...
}
...
.gesture(
DragGesture()
.onChanged { gesture in
offset = gesture.translation
feedback.prepare()
}
.onEnded { _ in
if abs(offset.width) > 100 {
// remove the card
if offset.width > 0 {
feedback.notificationOccurred(.success)
removal?(true)
} else {
feedback.notificationOccurred(.error)
removal?(false)
}
} else {
offset = .zero
}
}
)
...
}
}
struct ContentView: View {
...
var body: some View {
ZStack {
...
VStack {
...
ZStack {
ForEach(cards) { card in
CardView(card: card) { isCorrect in
withAnimation {
moveCard(card, isCorrect: isCorrect)
}
}
.stacked(at: findIndex(of: card), in: cards.count)
.allowsHitTesting(findIndex(of: card) == cards.count - 1)
.accessibilityHidden(findIndex(of: card) < cards.count - 1)
}
}
.allowsHitTesting(timeRemaining > 0)
...
}
...
}
...
}
func findIndex(of card: Card) -> Int {
cards.firstIndex(where: { $0.id == card.id }) ?? 0
}
func moveCard(_ card: Card, isCorrect: Bool) {
guard let index = cards.firstIndex(where: { $0.id == card.id }) else { return }
removeCard(at: index)
if isCorrect == false {
let reCard = Card(prompt: card.prompt, answer: card.answer)
cards.insert(reCard, at: 0)
}
}
func removeCard(at index: Int) {
guard index >= 0 else { return }
cards.remove(at: index)
if cards.isEmpty {
isActive = false
}
}
func resetCards() {
...
}
func loadData() {
...
}
}