Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions Sources/CardStack/CardStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,25 @@ where Data.Index: Hashable {
self._currentIndex = State<Data.Index>(initialValue: data.startIndex)
}

@ViewBuilder private func cardViewOrEmpty(index: Data.Index) -> some View {
let relativeIndex = self.data.distance(from: self.currentIndex, to: index)
if relativeIndex >= 0 && relativeIndex < self.configuration.maxVisibleCards {
self.card(index: index, relativeIndex: relativeIndex)
} else {
EmptyView()
}
}

public var body: some View {
ZStack {
ForEach(data.indices.reversed(), id: \.self) { index -> AnyView in
let relativeIndex = self.data.distance(from: self.currentIndex, to: index)
if relativeIndex >= 0 && relativeIndex < self.configuration.maxVisibleCards {
return AnyView(self.card(index: index, relativeIndex: relativeIndex))
} else {
return AnyView(EmptyView())
}
ForEach(data.indices.reversed(), id: \.self) { index in
cardViewOrEmpty(index: index)
.zIndex(Double(self.data.distance(from: index, to: self.data.startIndex)))
}
}
}


private func card(index: Data.Index, relativeIndex: Int) -> some View {
CardView(
direction: direction,
Expand Down
49 changes: 45 additions & 4 deletions Sources/CardStack/CardView.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import SwiftUI
import Combine

struct CardView<Direction, Content: View>: View {
@Environment(\.cardStackConfiguration) private var configuration: CardStackConfiguration
@State private var translation: CGSize = .zero
@State private var draggingState: CardDraggingState = .idle
@GestureState private var isDragging: Bool = false

private let direction: (Double) -> Direction?
private let isOnTop: Bool
private let onSwipe: (Direction) -> Void
private let content: (Direction?) -> Content

private enum CardDraggingState {
case dragging
case ended
case idle
}

init(
direction: @escaping (Double) -> Direction?,
isOnTop: Bool,
Expand All @@ -21,27 +30,59 @@ struct CardView<Direction, Content: View>: View {
self.content = content
}

var body: some View {
@ViewBuilder var cardView: some View {
GeometryReader { geometry in
self.content(self.swipeDirection(geometry))
.disabled(self.translation != .zero)
.offset(self.translation)
.rotationEffect(self.rotation(geometry))
.simultaneousGesture(self.isOnTop ? self.dragGesture(geometry) : nil)
.animation(draggingState == .dragging ? .easeInOut(duration: 0.05) : self.configuration.animation, value: translation)
}
.transition(transition)
}

private func cancelDragging() {
draggingState = .idle
translation = .zero
}

var body: some View {
if #available(iOS 14.0, *) {
cardView
.onChange(of: isDragging) { newValue in
if !newValue && draggingState == .dragging {
cancelDragging()
}
}
} else { // iOS 13.0, *
cardView
.onReceive(Just(isDragging)) { newValue in
if !newValue && draggingState == .dragging {
cancelDragging()
}
}
}
}

private func dragGesture(_ geometry: GeometryProxy) -> some Gesture {
DragGesture()
.updating($isDragging) { value, state, transaction in
state = true
}
.onChanged { value in
self.draggingState = .dragging
self.translation = value.translation
}
.onEnded { value in
self.translation = value.translation
draggingState = .ended
if let direction = self.swipeDirection(geometry) {
withAnimation(self.configuration.animation) { self.onSwipe(direction) }
self.translation = value.translation
withAnimation(self.configuration.animation) {
self.onSwipe(direction)
}
} else {
withAnimation { self.translation = .zero }
cancelDragging()
}
}
}
Expand Down
Loading