fix: swipe vs tap — use highPriorityGesture + hidden NavigationLink
- 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:
@@ -31,16 +31,18 @@ struct FitnessTabView: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
|
|
||||||
// Content — no page swipe (conflicts with swipe-to-delete)
|
// Content
|
||||||
Group {
|
TabView(selection: $selectedSubTab) {
|
||||||
switch selectedSubTab {
|
TodayView()
|
||||||
case 0: TodayView()
|
.tag(0)
|
||||||
case 1: TemplatesView()
|
TemplatesView()
|
||||||
case 2: GoalsView()
|
.tag(1)
|
||||||
case 3: FoodLibraryView()
|
GoalsView()
|
||||||
default: TodayView()
|
.tag(2)
|
||||||
}
|
FoodLibraryView()
|
||||||
|
.tag(3)
|
||||||
}
|
}
|
||||||
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||||
}
|
}
|
||||||
.background(Color.canvas)
|
.background(Color.canvas)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
|
|||||||
@@ -68,12 +68,12 @@ struct MealSectionView: View {
|
|||||||
|
|
||||||
if isExpanded {
|
if isExpanded {
|
||||||
ForEach(entries) { entry in
|
ForEach(entries) { entry in
|
||||||
SwipeToDeleteRow(onDelete: { onDelete(entry) }) {
|
SwipeToDeleteRow(
|
||||||
NavigationLink(destination: EntryDetailView(entry: entry, onDelete: {
|
onDelete: { onDelete(entry) },
|
||||||
onDelete(entry)
|
onTap: { /* handled by NavigationLink */ },
|
||||||
})) {
|
destination: { EntryDetailView(entry: entry, onDelete: { onDelete(entry) }) }
|
||||||
entryRow(entry)
|
) {
|
||||||
}
|
entryRow(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,40 +118,62 @@ struct MealSectionView: View {
|
|||||||
|
|
||||||
// MARK: - Swipe to Delete Row
|
// MARK: - Swipe to Delete Row
|
||||||
|
|
||||||
struct SwipeToDeleteRow<Content: View>: View {
|
struct SwipeToDeleteRow<Content: View, Destination: View>: View {
|
||||||
let onDelete: () -> Void
|
let onDelete: () -> Void
|
||||||
|
let onTap: () -> Void
|
||||||
|
let destination: () -> Destination
|
||||||
@ViewBuilder let content: () -> Content
|
@ViewBuilder let content: () -> Content
|
||||||
|
|
||||||
@State private var offset: CGFloat = 0
|
@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 {
|
var body: some View {
|
||||||
ZStack(alignment: .trailing) {
|
ZStack(alignment: .trailing) {
|
||||||
// Delete background
|
// Delete button background
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button {
|
Button {
|
||||||
withAnimation(.easeInOut(duration: 0.2)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
onDelete()
|
onDelete()
|
||||||
offset = 0
|
offset = 0
|
||||||
|
isSwiping = false
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "trash.fill")
|
Image(systemName: "trash.fill")
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.frame(width: 70, height: .infinity)
|
.frame(maxHeight: .infinity)
|
||||||
|
.frame(width: 70)
|
||||||
}
|
}
|
||||||
.frame(width: 70)
|
|
||||||
.background(Color.red)
|
.background(Color.red)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content
|
// Content row
|
||||||
content()
|
content()
|
||||||
.offset(x: offset)
|
.offset(x: offset)
|
||||||
.gesture(
|
.background(
|
||||||
DragGesture()
|
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
|
.onChanged { value in
|
||||||
if value.translation.width < 0 {
|
if value.translation.width < 0 {
|
||||||
offset = max(value.translation.width, -80)
|
offset = max(value.translation.width, -80)
|
||||||
} else if showDelete {
|
} else if isSwiping {
|
||||||
offset = min(value.translation.width - 70, 0)
|
offset = min(value.translation.width - 70, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,10 +181,10 @@ struct SwipeToDeleteRow<Content: View>: View {
|
|||||||
withAnimation(.easeOut(duration: 0.2)) {
|
withAnimation(.easeOut(duration: 0.2)) {
|
||||||
if value.translation.width < -40 {
|
if value.translation.width < -40 {
|
||||||
offset = -70
|
offset = -70
|
||||||
showDelete = true
|
isSwiping = true
|
||||||
} else {
|
} else {
|
||||||
offset = 0
|
offset = 0
|
||||||
showDelete = false
|
isSwiping = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user