listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

commit 1223f5a5baf3b80cf48328dd8a8b1eef30f70361
parent c21a1e4aeb11ec20f2d2316044b4cee4cbf1a2fe
Author: Michael Camilleri <[email protected]>
Date:   Thu, 19 Mar 2026 13:41:23 +0900

Fix further issues on iOS 26

The `.onTapGesture` used previously for the tap-to-create gesture does
not work on iOS 26. This commit uses a hacky alternative that detects
all taps on the ScrollView and then check if the tap is occurring
'below' the rows in the list.

Similarly, `@FetchRequest` previously triggered an update refresh when a
property (like completion) on a result was toggled. This no longer
occurs and so instead a counter is incremented to trigger the refresh.

Co-Authored-By: Claude 4.6 Opus <[email protected]>

Diffstat:
MListlessiOS/Views/TaskListView.swift | 24++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/ListlessiOS/Views/TaskListView.swift b/ListlessiOS/Views/TaskListView.swift @@ -15,6 +15,8 @@ struct TaskListView: View, TaskListViewProtocol { var isScrolling: Bool = false var draftPlacement: DraftTaskPlacement? var draftTitle: String = "" + var contentBottomY: CGFloat = 0 + var fetchWorkaround: Int = 0 } @AppStorage("headingText") var headingText = "Items" @@ -406,6 +408,7 @@ struct TaskListView: View, TaskListViewProtocol { } @ViewBuilder private var taskRows: some View { + let _ = iState.fetchWorkaround ForEach(Array(displayActiveTasks.enumerated()), id: \.element.id) { index, task in let taskID = task.id TaskRowView( @@ -418,7 +421,7 @@ struct TaskListView: View, TaskListViewProtocol { isScrolling: iState.isScrolling, isLastActiveTask: index == displayActiveTasks.count - 1, focusedField: $focusedFieldBinding, - onToggle: { toggleCompletion($0) }, + onToggle: { toggleCompletion($0); withAnimation { iState.fetchWorkaround &+= 1 } }, onTitleChange: { updateTitle($0, $1) }, onDelete: { deleteTaskWithUndo($0) }, onSelect: { selectTask($0) }, @@ -462,7 +465,7 @@ struct TaskListView: View, TaskListViewProtocol { isSelected: fState.selectedTaskID == taskID, isScrolling: iState.isScrolling, focusedField: $focusedFieldBinding, - onToggle: { toggleCompletion($0) }, + onToggle: { toggleCompletion($0); withAnimation { iState.fetchWorkaround &+= 1 } }, onTitleChange: { updateTitle($0, $1) }, onDelete: { deleteTaskWithUndo($0) }, onSelect: { selectTask($0) } @@ -475,10 +478,12 @@ struct TaskListView: View, TaskListViewProtocol { var body: some View { taskScrollView - .contentShape(Rectangle()) - .onTapGesture { - handleBackgroundTap() - } + .simultaneousGesture( + SpatialTapGesture(coordinateSpace: .global).onEnded { value in + guard value.location.y > iState.contentBottomY else { return } + handleBackgroundTap() + } + ) .accessibilityIdentifier("task-list-scrollview") .background { let isEditing = if case .task = focusedFieldBinding { true } else { false } @@ -559,6 +564,11 @@ struct TaskListView: View, TaskListViewProtocol { .offset(y: -pullToCreateRowOverlap) } .frame(maxWidth: .infinity, alignment: .topLeading) + .onGeometryChange(for: CGFloat.self) { + $0.frame(in: .global).maxY + } action: { + iState.contentBottomY = $0 + } .padding(.trailing, 16) .padding(.vertical, 12) .offset(y: -iState.pullToCreate.pullOffset) @@ -641,3 +651,5 @@ struct TaskListView: View, TaskListViewProtocol { ) } } + +