Skip to main content

Swipeable Kartenansicht

Beschreibung

Diese View zeigt einen Stapel von Karten, die Nutzer:innen nach links oder rechts wischen können, um sie zu entfernen. Jede Karte stellt ein anderes Element oder einen Inhalt dar und bietet eine interaktive Möglichkeit, durch verschiedene Optionen zu blättern.

🔍 Zweck

  • Matching-Apps mit Swipe-Funktion (z.B. Dating, Jobsuche)
  • Durchblättern von Produktvorschlägen oder Empfehlungen
  • Quizfragen oder Lernkarten anzeigen und verwerfen
  • Bewertung/Auswahl von Ideen oder Vorschlägen im Team
  • Spielerische Onboarding-Erfahrungen gestalten

📄 Codebeispiel

import SwiftUI

// Model for card content
struct CardItem: Identifiable {
    let id = UUID()
    let title: String
    let subtitle: String
    let color: Color
}

struct SwipeableCardsView: View {
    @State private var cards: [CardItem] = [
        CardItem(title: "Karte 1", subtitle: "Erste Information", color: .blue),
        CardItem(title: "Karte 2", subtitle: "Zweite Information", color: .purple),
        CardItem(title: "Karte 3", subtitle: "Dritte Information", color: .green),
        CardItem(title: "Karte 4", subtitle: "Vierte Information", color: .orange),
        CardItem(title: "Karte 5", subtitle: "Fünfte Information", color: .pink)
    ]
    
    var body: some View {
        ZStack {
            ForEach(Array(cards.enumerated()), id: \.element.id) { (index, card) in
                SwipeableCard(
                    card: card,
                    onRemove: { removedCard in
                        withAnimation(.spring()) {
                            if let idx = cards.firstIndex(where: { $0.id == removedCard.id }) {
                                cards.remove(at: idx)
                            }
                        }
                    }
                )
                // Stack effect for depth perception
                .offset(y: CGFloat(index) * 8)
                .scaleEffect(1 - CGFloat(index) * 0.04)
                // Ensure first card is on top
                .zIndex(Double(cards.count - index))
            }
            
            if cards.isEmpty {
                VStack(spacing: 16) {
                    Image(systemName: "checkmark.seal.fill")
                        .font(.system(size: 48))
                        .foregroundStyle(.green)
                    Text("Alle Karten erledigt!")
                        .font(.title2)
                        .bold()
                }
            }
        }
        .padding(.vertical, 40)
        .padding(.horizontal, 24)
        .animation(.spring(), value: cards.count)
        .frame(maxWidth: 400, maxHeight: 600)
        // Accessibility improvement for screen readers:
        .accessibilityElement(children: .contain)
    }
}

// Single swipeable card view with drag gesture logic
struct SwipeableCard: View {
    let card: CardItem
    let onRemove: (CardItem) -> Void
    
    @State private var offset = CGSize.zero
    
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(card.title)
                .font(.title2).bold()
            
            Text(card.subtitle)
                .font(.body)
            
            Spacer()
            
            HStack {
                Spacer()
                Image(systemName:"chevron.left.slash.chevron.right")
                    .font(.largeTitle.bold())
                    .opacity(0.08)
                Spacer()
            }
        }
        .padding(28)
        .frame(width: 320, height: 440)
        .background(card.color.gradient.opacity(0.92))
        .clipShape(RoundedRectangle(cornerRadius: 22))
        // Shadow and scaling while dragging for feedback
        .shadow(radius: offset == .zero ? 8 : 18)
        // Move the card following user's drag gesture
        .offset(x: offset.width, y: offset.height / 12)
        // Rotate slightly based on horizontal drag distance
        .rotationEffect(.degrees(Double(offset.width / 20)))
        
        // Gesture recognition for swiping left/right to dismiss the card.
        // When dragged far enough horizontally, trigger removal.
        .gesture(
            DragGesture()
                .onChanged { gesture in self.offset = gesture.translation }
                .onEnded { gesture in
                    if abs(gesture.translation.width) > 120 {
                        withAnimation(.spring()) {
                            offset.width = gesture.translation.width > 0 ? 800 : -800
                            onRemove(card)
                        }
                    } else {
                        withAnimation(.spring()) { offset = .zero }
                    }
                }
        )
        
        // Accessibility support for dismissing via actions:
        .accessibilityAction(named:"Karte entfernen") { onRemove(card) }
    }
}

#Preview {
    SwipeableCardsView()
}