fix: swipe vs tap — use highPriorityGesture + hidden NavigationLink
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s

- DragGesture with minimumDistance:15 as highPriorityGesture
- Tap only navigates when not swiping
- Tapping while swiped closes the delete button
- Hidden NavigationLink for programmatic navigation
- Reverted FitnessTabView back to page-style TabView

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 11:25:55 -05:00
parent c448b945d5
commit 2c5311264b
2 changed files with 50 additions and 26 deletions

View File

@@ -31,16 +31,18 @@ struct FitnessTabView: View {
.padding(.horizontal)
.padding(.top, 8)
// Content no page swipe (conflicts with swipe-to-delete)
Group {
switch selectedSubTab {
case 0: TodayView()
case 1: TemplatesView()
case 2: GoalsView()
case 3: FoodLibraryView()
default: TodayView()
}
// Content
TabView(selection: $selectedSubTab) {
TodayView()
.tag(0)
TemplatesView()
.tag(1)
GoalsView()
.tag(2)
FoodLibraryView()
.tag(3)
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.background(Color.canvas)
.navigationBarHidden(true)

View File

@@ -68,12 +68,12 @@ struct MealSectionView: View {
if isExpanded {
ForEach(entries) { entry in
SwipeToDeleteRow(onDelete: { onDelete(entry) }) {
NavigationLink(destination: EntryDetailView(entry: entry, onDelete: {
onDelete(entry)
})) {
entryRow(entry)
}
SwipeToDeleteRow(
onDelete: { onDelete(entry) },
onTap: { /* handled by NavigationLink */ },
destination: { EntryDetailView(entry: entry, onDelete: { onDelete(entry) }) }
) {
entryRow(entry)
}
}
}
@@ -118,40 +118,62 @@ struct MealSectionView: View {
// MARK: - Swipe to Delete Row
struct SwipeToDeleteRow<Content: View>: View {
struct SwipeToDeleteRow<Content: View, Destination: View>: View {
let onDelete: () -> Void
let onTap: () -> Void
let destination: () -> Destination
@ViewBuilder let content: () -> Content
@State private var offset: CGFloat = 0
@State private var showDelete = false
@State private var isSwiping = false
@State private var showDetail = false
var body: some View {
ZStack(alignment: .trailing) {
// Delete background
// Delete button background
HStack {
Spacer()
Button {
withAnimation(.easeInOut(duration: 0.2)) {
onDelete()
offset = 0
isSwiping = false
}
} label: {
Image(systemName: "trash.fill")
.foregroundStyle(.white)
.frame(width: 70, height: .infinity)
.frame(maxHeight: .infinity)
.frame(width: 70)
}
.frame(width: 70)
.background(Color.red)
}
// Content
// Content row
content()
.offset(x: offset)
.gesture(
DragGesture()
.background(
NavigationLink(destination: destination(), isActive: $showDetail) {
EmptyView()
}
.opacity(0)
)
.onTapGesture {
if !isSwiping {
showDetail = true
} else {
// Close swipe
withAnimation(.easeOut(duration: 0.2)) {
offset = 0
isSwiping = false
}
}
}
.highPriorityGesture(
DragGesture(minimumDistance: 15)
.onChanged { value in
if value.translation.width < 0 {
offset = max(value.translation.width, -80)
} else if showDelete {
} else if isSwiping {
offset = min(value.translation.width - 70, 0)
}
}
@@ -159,10 +181,10 @@ struct SwipeToDeleteRow<Content: View>: View {
withAnimation(.easeOut(duration: 0.2)) {
if value.translation.width < -40 {
offset = -70
showDelete = true
isSwiping = true
} else {
offset = 0
showDelete = false
isSwiping = false
}
}
}